@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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.BaseMessageController = void 0;
|
|
4
4
|
const _1 = require("./");
|
|
5
|
+
const access_control_1 = require("./access-control");
|
|
5
6
|
class BaseMessageController {
|
|
6
7
|
constructor(serviceName, serviceInstances, methodHandlers, options) {
|
|
7
8
|
this.methodRegistry = {};
|
|
@@ -11,6 +12,7 @@ class BaseMessageController {
|
|
|
11
12
|
this.beforeHook = options?.onBefore;
|
|
12
13
|
this.afterHook = options?.onAfter;
|
|
13
14
|
this.debug = options?.debug || false;
|
|
15
|
+
this.accessControl = options?.accessControl;
|
|
14
16
|
this.systemBeforeHook = (context) => {
|
|
15
17
|
if (this.debug) {
|
|
16
18
|
console.log(`[${this.constructor.name}] Received:`, {
|
|
@@ -67,6 +69,50 @@ class BaseMessageController {
|
|
|
67
69
|
throw error;
|
|
68
70
|
}
|
|
69
71
|
}
|
|
72
|
+
suggestClosestMethod(method) {
|
|
73
|
+
const candidates = Object.keys(this.methodRegistry || {});
|
|
74
|
+
if (!candidates.length) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const normalized = method.toLowerCase();
|
|
78
|
+
let best = null;
|
|
79
|
+
for (const candidate of candidates) {
|
|
80
|
+
const score = this.levenshteinDistance(normalized, candidate.toLowerCase());
|
|
81
|
+
if (!best || score < best.score) {
|
|
82
|
+
best = { name: candidate, score };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (!best) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const threshold = Math.max(2, Math.floor(method.length * 0.4));
|
|
89
|
+
return best.score <= threshold ? best.name : null;
|
|
90
|
+
}
|
|
91
|
+
levenshteinDistance(a, b) {
|
|
92
|
+
if (a === b) {
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
if (!a.length) {
|
|
96
|
+
return b.length;
|
|
97
|
+
}
|
|
98
|
+
if (!b.length) {
|
|
99
|
+
return a.length;
|
|
100
|
+
}
|
|
101
|
+
const matrix = Array.from({ length: a.length + 1 }, () => []);
|
|
102
|
+
for (let i = 0; i <= a.length; i++) {
|
|
103
|
+
matrix[i][0] = i;
|
|
104
|
+
}
|
|
105
|
+
for (let j = 0; j <= b.length; j++) {
|
|
106
|
+
matrix[0][j] = j;
|
|
107
|
+
}
|
|
108
|
+
for (let i = 1; i <= a.length; i++) {
|
|
109
|
+
for (let j = 1; j <= b.length; j++) {
|
|
110
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
111
|
+
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return matrix[a.length][b.length];
|
|
115
|
+
}
|
|
70
116
|
async formatResult(result) {
|
|
71
117
|
if (result instanceof Promise) {
|
|
72
118
|
result = await result;
|
|
@@ -100,12 +146,13 @@ class BaseMessageController {
|
|
|
100
146
|
};
|
|
101
147
|
}
|
|
102
148
|
async processMessage(data) {
|
|
103
|
-
const { method, uuid, params } = this.extractMessageData(data);
|
|
149
|
+
const { method, uuid, params, meta } = this.extractMessageData(data);
|
|
104
150
|
const baseContext = {
|
|
105
151
|
method,
|
|
106
152
|
serviceName: this.serviceName,
|
|
107
153
|
uuid,
|
|
108
|
-
rawData: data
|
|
154
|
+
rawData: data,
|
|
155
|
+
meta
|
|
109
156
|
};
|
|
110
157
|
const requestContext = {
|
|
111
158
|
...baseContext,
|
|
@@ -120,10 +167,26 @@ class BaseMessageController {
|
|
|
120
167
|
processedParams = hookResult;
|
|
121
168
|
}
|
|
122
169
|
}
|
|
170
|
+
const callerService = (0, access_control_1.extractCallerService)(meta);
|
|
171
|
+
const topic = this.serviceName;
|
|
172
|
+
if (!(0, access_control_1.isAccessAllowed)(this.accessControl, topic, method, callerService)) {
|
|
173
|
+
(0, access_control_1.logAccessDenied)(this.accessControl, { topic, method, serviceName: this.serviceName, callerService });
|
|
174
|
+
return {
|
|
175
|
+
uuid,
|
|
176
|
+
method,
|
|
177
|
+
params: {
|
|
178
|
+
result: "error",
|
|
179
|
+
error: (0, access_control_1.createAccessDeniedError)(method, this.serviceName, callerService)
|
|
180
|
+
},
|
|
181
|
+
meta
|
|
182
|
+
};
|
|
183
|
+
}
|
|
123
184
|
const handler = this.methodRegistry[method];
|
|
124
185
|
if (!handler) {
|
|
186
|
+
const suggestion = this.suggestClosestMethod(method);
|
|
187
|
+
const message = suggestion ? `Invalid method name '${method}', did you mean '${suggestion}'?` : `Method handler not found: ${method}`;
|
|
125
188
|
throw new _1.MessagingError(_1.ErrorCode.UNKNOWN, {
|
|
126
|
-
message
|
|
189
|
+
message
|
|
127
190
|
});
|
|
128
191
|
}
|
|
129
192
|
const result = await this.executeHandler(handler, processedParams);
|
|
@@ -131,7 +194,8 @@ class BaseMessageController {
|
|
|
131
194
|
let response = {
|
|
132
195
|
uuid,
|
|
133
196
|
method,
|
|
134
|
-
params: { result: formattedResult }
|
|
197
|
+
params: { result: formattedResult },
|
|
198
|
+
meta
|
|
135
199
|
};
|
|
136
200
|
const responseContext = {
|
|
137
201
|
...baseContext,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_DISCOVERY_TOPIC = exports.DEFAULT_BROADCAST_TOPIC = exports.DEFAULT_EVENTS_SUFFIX = exports.DEFAULT_SUBSCRIPTION_SUFFIX = void 0;
|
|
4
|
+
exports.DEFAULT_SUBSCRIPTION_SUFFIX = "-events.sub";
|
|
5
|
+
exports.DEFAULT_EVENTS_SUFFIX = "-events";
|
|
6
|
+
exports.DEFAULT_BROADCAST_TOPIC = "__broadcast";
|
|
7
|
+
exports.DEFAULT_DISCOVERY_TOPIC = "__nevo.discovery";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { DiscoveryAnnouncement, DiscoveryEntry } from "./types";
|
|
2
|
+
export declare class DiscoveryRegistry {
|
|
3
|
+
private readonly services;
|
|
4
|
+
update(announcement: DiscoveryAnnouncement): void;
|
|
5
|
+
prune(ttlMs: number): void;
|
|
6
|
+
list(): DiscoveryEntry[];
|
|
7
|
+
isAvailable(serviceName: string, ttlMs: number): boolean;
|
|
8
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DiscoveryRegistry = void 0;
|
|
4
|
+
class DiscoveryRegistry {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.services = new Map();
|
|
7
|
+
}
|
|
8
|
+
update(announcement) {
|
|
9
|
+
const now = Date.now();
|
|
10
|
+
const entry = {
|
|
11
|
+
...announcement,
|
|
12
|
+
lastSeen: now
|
|
13
|
+
};
|
|
14
|
+
this.services.set(announcement.serviceName, entry);
|
|
15
|
+
}
|
|
16
|
+
prune(ttlMs) {
|
|
17
|
+
const now = Date.now();
|
|
18
|
+
for (const [serviceName, entry] of this.services.entries()) {
|
|
19
|
+
if (now - entry.lastSeen > ttlMs) {
|
|
20
|
+
this.services.delete(serviceName);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
list() {
|
|
25
|
+
return [...this.services.values()];
|
|
26
|
+
}
|
|
27
|
+
isAvailable(serviceName, ttlMs) {
|
|
28
|
+
const entry = this.services.get(serviceName);
|
|
29
|
+
if (!entry) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return Date.now() - entry.lastSeen <= ttlMs;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.DiscoveryRegistry = DiscoveryRegistry;
|
|
@@ -3,5 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ErrorMessages = void 0;
|
|
4
4
|
const _1 = require("./");
|
|
5
5
|
exports.ErrorMessages = {
|
|
6
|
-
[_1.ErrorCode.UNKNOWN]: "An unknown error occurred"
|
|
6
|
+
[_1.ErrorCode.UNKNOWN]: "An unknown error occurred",
|
|
7
|
+
[_1.ErrorCode.UNAUTHORIZED]: "Access denied"
|
|
7
8
|
};
|
package/dist/common/index.d.ts
CHANGED
package/dist/common/index.js
CHANGED
|
@@ -22,3 +22,6 @@ __exportStar(require("./service-utils"), exports);
|
|
|
22
22
|
__exportStar(require("./error-code"), exports);
|
|
23
23
|
__exportStar(require("./error-messages"), exports);
|
|
24
24
|
__exportStar(require("./bigint.utils"), exports);
|
|
25
|
+
__exportStar(require("./constants"), exports);
|
|
26
|
+
__exportStar(require("./access-control"), exports);
|
|
27
|
+
__exportStar(require("./discovery"), exports);
|
|
@@ -17,5 +17,7 @@ export declare function mapServiceMethods<T>(service: T, customMappings?: Record
|
|
|
17
17
|
export declare const createServiceClient: <T extends ServiceMethodMap>(serviceName: string) => {
|
|
18
18
|
query: <M extends keyof T & string>(method: M, params: T[M]["params"]) => Promise<T[M]["result"]>;
|
|
19
19
|
emit: <M extends keyof T & string>(method: M, params: T[M]["params"]) => Promise<void>;
|
|
20
|
+
publish: <M extends keyof T & string>(method: M, params: T[M]["params"]) => Promise<void>;
|
|
21
|
+
subscribe: <M extends keyof T & string>(method: M, handler: (data: T[M]["result"]) => void) => Promise<void>;
|
|
20
22
|
};
|
|
21
23
|
export declare function createMethodHandlers(mappings: Record<string, [string, ((params: unknown) => unknown[])?, ((result: unknown) => unknown)?]>): ServiceMethodMapping;
|
|
@@ -38,6 +38,14 @@ const createServiceClient = (serviceName) => ({
|
|
|
38
38
|
// @ts-ignore
|
|
39
39
|
emit: (method, params) => {
|
|
40
40
|
throw new Error("Implementation required in subclass");
|
|
41
|
+
},
|
|
42
|
+
// @ts-ignore
|
|
43
|
+
publish: (method, params) => {
|
|
44
|
+
throw new Error("Implementation required in subclass");
|
|
45
|
+
},
|
|
46
|
+
// @ts-ignore
|
|
47
|
+
subscribe: (method, handler) => {
|
|
48
|
+
throw new Error("Implementation required in subclass");
|
|
41
49
|
}
|
|
42
50
|
});
|
|
43
51
|
exports.createServiceClient = createServiceClient;
|
package/dist/common/types.d.ts
CHANGED
|
@@ -6,10 +6,20 @@ export interface MessagePayload {
|
|
|
6
6
|
key: string;
|
|
7
7
|
value: string;
|
|
8
8
|
}
|
|
9
|
+
export type MessageType = "query" | "emit" | "sub" | "broadcast" | "discovery";
|
|
10
|
+
export interface MessageMeta {
|
|
11
|
+
type?: MessageType;
|
|
12
|
+
service?: string;
|
|
13
|
+
ts?: number;
|
|
14
|
+
auth?: {
|
|
15
|
+
token?: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
9
18
|
export interface MessageRequest<T = unknown> {
|
|
10
19
|
uuid: string;
|
|
11
20
|
method: string;
|
|
12
21
|
params: T;
|
|
22
|
+
meta?: MessageMeta;
|
|
13
23
|
}
|
|
14
24
|
export interface MessageResponse<T = unknown> {
|
|
15
25
|
uuid: string;
|
|
@@ -18,6 +28,7 @@ export interface MessageResponse<T = unknown> {
|
|
|
18
28
|
result: T | "error";
|
|
19
29
|
error?: ErrorDetails;
|
|
20
30
|
};
|
|
31
|
+
meta?: MessageMeta;
|
|
21
32
|
}
|
|
22
33
|
export interface ErrorDetails {
|
|
23
34
|
code: number;
|
|
@@ -31,6 +42,7 @@ export interface HookContext {
|
|
|
31
42
|
serviceName: string;
|
|
32
43
|
uuid: string;
|
|
33
44
|
rawData: unknown;
|
|
45
|
+
meta?: MessageMeta;
|
|
34
46
|
}
|
|
35
47
|
export interface BeforeHookContext extends HookContext {
|
|
36
48
|
params: unknown;
|
|
@@ -57,16 +69,66 @@ export interface ServiceMethodMapping {
|
|
|
57
69
|
}
|
|
58
70
|
export interface TransportClientOptions {
|
|
59
71
|
clientId?: string;
|
|
72
|
+
serviceName?: string;
|
|
73
|
+
authToken?: string;
|
|
60
74
|
timeout?: number;
|
|
61
75
|
debug?: boolean;
|
|
76
|
+
backoff?: {
|
|
77
|
+
enabled?: boolean;
|
|
78
|
+
baseMs?: number;
|
|
79
|
+
maxMs?: number;
|
|
80
|
+
maxAttempts?: number;
|
|
81
|
+
jitter?: boolean;
|
|
82
|
+
};
|
|
83
|
+
discovery?: {
|
|
84
|
+
enabled?: boolean;
|
|
85
|
+
heartbeatIntervalMs?: number;
|
|
86
|
+
ttlMs?: number;
|
|
87
|
+
};
|
|
62
88
|
[key: string]: any;
|
|
63
89
|
}
|
|
64
90
|
export interface TransportServerOptions {
|
|
65
91
|
serviceName: string;
|
|
66
92
|
debug?: boolean;
|
|
93
|
+
authToken?: string;
|
|
67
94
|
[key: string]: any;
|
|
68
95
|
}
|
|
69
96
|
export interface MicroserviceConfig {
|
|
70
97
|
serviceName: string;
|
|
71
98
|
clientName: string;
|
|
72
99
|
}
|
|
100
|
+
export interface SubscriptionOptions {
|
|
101
|
+
ack?: boolean;
|
|
102
|
+
durableKey?: string;
|
|
103
|
+
groupId?: string;
|
|
104
|
+
fromBeginning?: boolean;
|
|
105
|
+
}
|
|
106
|
+
export interface SubscriptionContext {
|
|
107
|
+
ack(): Promise<void>;
|
|
108
|
+
nack?(reason?: string): Promise<void>;
|
|
109
|
+
meta: MessageMeta;
|
|
110
|
+
}
|
|
111
|
+
export interface Subscription {
|
|
112
|
+
unsubscribe(): Promise<void>;
|
|
113
|
+
}
|
|
114
|
+
export interface AccessRule {
|
|
115
|
+
topic?: string;
|
|
116
|
+
method?: string;
|
|
117
|
+
allow?: string[];
|
|
118
|
+
deny?: string[];
|
|
119
|
+
}
|
|
120
|
+
export interface AccessControlConfig {
|
|
121
|
+
rules?: AccessRule[];
|
|
122
|
+
allowAllByDefault?: boolean;
|
|
123
|
+
logDenied?: boolean;
|
|
124
|
+
}
|
|
125
|
+
export interface DiscoveryAnnouncement {
|
|
126
|
+
serviceName: string;
|
|
127
|
+
clientId?: string;
|
|
128
|
+
transport: string;
|
|
129
|
+
ts: number;
|
|
130
|
+
meta?: Record<string, unknown>;
|
|
131
|
+
}
|
|
132
|
+
export interface DiscoveryEntry extends DiscoveryAnnouncement {
|
|
133
|
+
lastSeen: number;
|
|
134
|
+
}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { Type } from "@nestjs/common";
|
|
2
|
-
import { BeforeHook, AfterHook } from "./common";
|
|
2
|
+
import { BeforeHook, AfterHook, AccessControlConfig, MessageMeta } from "./common";
|
|
3
3
|
export interface SignalRouterOptions {
|
|
4
4
|
before?: BeforeHook;
|
|
5
5
|
after?: AfterHook;
|
|
6
6
|
debug?: boolean;
|
|
7
7
|
eventPattern?: string;
|
|
8
|
+
accessControl?: AccessControlConfig;
|
|
8
9
|
}
|
|
9
10
|
export interface MessageData {
|
|
10
11
|
method: string;
|
|
11
12
|
params: any;
|
|
12
13
|
uuid: string;
|
|
14
|
+
meta?: MessageMeta;
|
|
13
15
|
}
|
|
14
16
|
export type MessageExtractor = (data: any) => MessageData;
|
|
15
17
|
export declare function findPropertyByType(obj: any, type: Type<any>): string | null;
|
|
@@ -5,6 +5,8 @@ exports.findServiceInstances = findServiceInstances;
|
|
|
5
5
|
exports.createErrorResponse = createErrorResponse;
|
|
6
6
|
exports.createSignalRouterDecorator = createSignalRouterDecorator;
|
|
7
7
|
const common_1 = require("./common");
|
|
8
|
+
const access_control_1 = require("./common/access-control");
|
|
9
|
+
const common_2 = require("./common");
|
|
8
10
|
const signal_decorator_1 = require("./signal.decorator");
|
|
9
11
|
function findPropertyByType(obj, type) {
|
|
10
12
|
for (const prop in obj) {
|
|
@@ -34,6 +36,49 @@ function createErrorResponse(message, uuid, method, code = 0) {
|
|
|
34
36
|
}
|
|
35
37
|
};
|
|
36
38
|
}
|
|
39
|
+
function levenshteinDistance(a, b) {
|
|
40
|
+
if (a === b) {
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
if (!a.length) {
|
|
44
|
+
return b.length;
|
|
45
|
+
}
|
|
46
|
+
if (!b.length) {
|
|
47
|
+
return a.length;
|
|
48
|
+
}
|
|
49
|
+
const matrix = Array.from({ length: a.length + 1 }, () => []);
|
|
50
|
+
for (let i = 0; i <= a.length; i++) {
|
|
51
|
+
matrix[i][0] = i;
|
|
52
|
+
}
|
|
53
|
+
for (let j = 0; j <= b.length; j++) {
|
|
54
|
+
matrix[0][j] = j;
|
|
55
|
+
}
|
|
56
|
+
for (let i = 1; i <= a.length; i++) {
|
|
57
|
+
for (let j = 1; j <= b.length; j++) {
|
|
58
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
59
|
+
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return matrix[a.length][b.length];
|
|
63
|
+
}
|
|
64
|
+
function suggestClosestMethod(method, candidates) {
|
|
65
|
+
if (!candidates.length) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const normalized = method.toLowerCase();
|
|
69
|
+
let best = null;
|
|
70
|
+
for (const candidate of candidates) {
|
|
71
|
+
const score = levenshteinDistance(normalized, candidate.toLowerCase());
|
|
72
|
+
if (!best || score < best.score) {
|
|
73
|
+
best = { name: candidate, score };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (!best) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const threshold = Math.max(2, Math.floor(method.length * 0.4));
|
|
80
|
+
return best.score <= threshold ? best.name : null;
|
|
81
|
+
}
|
|
37
82
|
function createSignalRouterDecorator(serviceType, options = {}, messageExtractor, registerHandler) {
|
|
38
83
|
const debug = options?.debug || process.env["NODE_ENV"] !== "production";
|
|
39
84
|
return function (target) {
|
|
@@ -45,7 +90,7 @@ function createSignalRouterDecorator(serviceType, options = {}, messageExtractor
|
|
|
45
90
|
console.log(`[${eventPattern}] Received message:`, (0, common_1.stringifyWithBigInt)(data));
|
|
46
91
|
}
|
|
47
92
|
const messageData = messageExtractor(data);
|
|
48
|
-
const { method, params, uuid } = messageData;
|
|
93
|
+
const { method, params, uuid, meta } = messageData;
|
|
49
94
|
if (!method) {
|
|
50
95
|
console.error("Missing 'method' field in message");
|
|
51
96
|
return createErrorResponse("Invalid message format");
|
|
@@ -58,6 +103,20 @@ function createSignalRouterDecorator(serviceType, options = {}, messageExtractor
|
|
|
58
103
|
console.error(`No service instances found for:`, serviceType);
|
|
59
104
|
return createErrorResponse("Service not found", uuid, method);
|
|
60
105
|
}
|
|
106
|
+
const callerService = (0, access_control_1.extractCallerService)(meta);
|
|
107
|
+
const topic = eventPattern;
|
|
108
|
+
if (!(0, access_control_1.isAccessAllowed)(options.accessControl, topic, method, callerService)) {
|
|
109
|
+
(0, access_control_1.logAccessDenied)(options.accessControl, { topic, method, serviceName: eventPattern, callerService });
|
|
110
|
+
return {
|
|
111
|
+
uuid,
|
|
112
|
+
method,
|
|
113
|
+
params: {
|
|
114
|
+
result: "error",
|
|
115
|
+
error: (0, access_control_1.createAccessDeniedError)(method, eventPattern, callerService)
|
|
116
|
+
},
|
|
117
|
+
meta
|
|
118
|
+
};
|
|
119
|
+
}
|
|
61
120
|
let processedParams = params;
|
|
62
121
|
if (options.before) {
|
|
63
122
|
const hookResult = await options.before({
|
|
@@ -65,7 +124,8 @@ function createSignalRouterDecorator(serviceType, options = {}, messageExtractor
|
|
|
65
124
|
serviceName: eventPattern,
|
|
66
125
|
uuid,
|
|
67
126
|
rawData: data,
|
|
68
|
-
params
|
|
127
|
+
params,
|
|
128
|
+
meta
|
|
69
129
|
});
|
|
70
130
|
if (hookResult !== undefined) {
|
|
71
131
|
processedParams = hookResult;
|
|
@@ -75,7 +135,9 @@ function createSignalRouterDecorator(serviceType, options = {}, messageExtractor
|
|
|
75
135
|
const signalHandler = signals.find((s) => s.signalName === method);
|
|
76
136
|
if (!signalHandler) {
|
|
77
137
|
console.error(`No handler found for method:`, method);
|
|
78
|
-
|
|
138
|
+
const suggestion = suggestClosestMethod(method, signals.map((s) => s.signalName));
|
|
139
|
+
const message = suggestion ? `Invalid method name '${method}', did you mean '${suggestion}'?` : `Method ${method} not found`;
|
|
140
|
+
return createErrorResponse(message, uuid, method);
|
|
79
141
|
}
|
|
80
142
|
let serviceInstance = null;
|
|
81
143
|
const serviceMethod = signalHandler.methodName;
|
|
@@ -101,7 +163,8 @@ function createSignalRouterDecorator(serviceType, options = {}, messageExtractor
|
|
|
101
163
|
let response = {
|
|
102
164
|
uuid,
|
|
103
165
|
method,
|
|
104
|
-
params: { result: serializedResult }
|
|
166
|
+
params: { result: serializedResult },
|
|
167
|
+
meta
|
|
105
168
|
};
|
|
106
169
|
if (options.after) {
|
|
107
170
|
const hookResponse = await options.after({
|
|
@@ -111,7 +174,8 @@ function createSignalRouterDecorator(serviceType, options = {}, messageExtractor
|
|
|
111
174
|
rawData: data,
|
|
112
175
|
params: processedParams,
|
|
113
176
|
result: serializedResult,
|
|
114
|
-
response
|
|
177
|
+
response,
|
|
178
|
+
meta
|
|
115
179
|
});
|
|
116
180
|
if (hookResponse !== undefined) {
|
|
117
181
|
response = hookResponse;
|
|
@@ -122,7 +186,7 @@ function createSignalRouterDecorator(serviceType, options = {}, messageExtractor
|
|
|
122
186
|
catch (error) {
|
|
123
187
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
124
188
|
console.error(`[${eventPattern}] Processing error:`, error);
|
|
125
|
-
return createErrorResponse(errorMessage, data.uuid, data.method, error.code);
|
|
189
|
+
return createErrorResponse(errorMessage, data.uuid, data.method, error.code || common_2.ErrorCode.UNKNOWN);
|
|
126
190
|
}
|
|
127
191
|
};
|
|
128
192
|
registerHandler(target, eventPattern, handlerName);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NevoHttpClient } from "./nevo-http.client";
|
|
2
|
+
export declare abstract class HttpClientBase {
|
|
3
|
+
protected readonly universalClient: NevoHttpClient;
|
|
4
|
+
protected constructor(universalClient: NevoHttpClient);
|
|
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<NevoHttpClient["subscribe"]>[2], handler: Parameters<NevoHttpClient["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.HttpClientBase = void 0;
|
|
4
|
+
class HttpClientBase {
|
|
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.HttpClientBase = HttpClientBase;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { NevoHttpClient, NevoHttpClientOptions } from "./nevo-http.client";
|
|
2
|
+
export interface HttpClientFactoryOptions extends NevoHttpClientOptions {
|
|
3
|
+
clientIdPrefix: string;
|
|
4
|
+
}
|
|
5
|
+
export declare const createNevoHttpClient: (serviceUrls: Record<string, string>, options: HttpClientFactoryOptions) => {
|
|
6
|
+
provide: string;
|
|
7
|
+
useFactory: () => Promise<NevoHttpClient>;
|
|
8
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createNevoHttpClient = void 0;
|
|
4
|
+
const nevo_http_client_1 = require("./nevo-http.client");
|
|
5
|
+
const createNevoHttpClient = (serviceUrls, options) => {
|
|
6
|
+
return {
|
|
7
|
+
provide: "NEVO_HTTP_CLIENT",
|
|
8
|
+
useFactory: async () => {
|
|
9
|
+
return new nevo_http_client_1.NevoHttpClient(serviceUrls, {
|
|
10
|
+
...options,
|
|
11
|
+
serviceName: options.serviceName || options.clientIdPrefix
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
exports.createNevoHttpClient = createNevoHttpClient;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HttpSignalRouter = HttpSignalRouter;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
const signal_router_utils_1 = require("../../signal-router.utils");
|
|
6
|
+
function HttpSignalRouter(serviceType, options) {
|
|
7
|
+
return (0, signal_router_utils_1.createSignalRouterDecorator)(serviceType, options, (data) => {
|
|
8
|
+
const messageData = data || {};
|
|
9
|
+
return {
|
|
10
|
+
method: messageData.method,
|
|
11
|
+
params: messageData.params,
|
|
12
|
+
uuid: messageData.uuid,
|
|
13
|
+
meta: messageData.meta
|
|
14
|
+
};
|
|
15
|
+
}, (target, eventPattern, handlerName) => {
|
|
16
|
+
(0, common_1.Post)(`/${eventPattern}`)(target.prototype, handlerName, Object.getOwnPropertyDescriptor(target.prototype, handlerName));
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Observable } from "rxjs";
|
|
2
|
+
export declare class HttpTransportController {
|
|
3
|
+
streamDiscovery(): Observable<{
|
|
4
|
+
data: string;
|
|
5
|
+
}>;
|
|
6
|
+
publishDiscovery(payload: any): {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
};
|
|
9
|
+
streamSubscription(service: string): Observable<{
|
|
10
|
+
data: string;
|
|
11
|
+
}>;
|
|
12
|
+
publishSubscription(payload: any): {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
};
|
|
15
|
+
streamBroadcast(): Observable<{
|
|
16
|
+
data: string;
|
|
17
|
+
}>;
|
|
18
|
+
publishBroadcast(payload: any): {
|
|
19
|
+
ok: boolean;
|
|
20
|
+
};
|
|
21
|
+
}
|