@rsdk/nats.transport 5.7.0 → 5.8.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/events.deserializer.d.ts +4 -3
- package/dist/events.deserializer.js +2 -2
- package/dist/events.deserializer.js.map +1 -1
- package/dist/index.d.ts +2 -4
- package/dist/index.js +11 -7
- package/dist/index.js.map +1 -1
- package/dist/jetstream/constants.d.ts +5 -0
- package/dist/jetstream/constants.js +9 -0
- package/dist/jetstream/constants.js.map +1 -0
- package/dist/{metadata.decorator.d.ts → jetstream/consume.decorator.d.ts} +1 -1
- package/dist/{metadata.decorator.js → jetstream/consume.decorator.js} +2 -2
- package/dist/jetstream/consume.decorator.js.map +1 -0
- package/dist/jetstream/index.d.ts +7 -0
- package/dist/jetstream/index.js +24 -0
- package/dist/jetstream/index.js.map +1 -0
- package/dist/{nats-jetstream-errors.formatter.d.ts → jetstream/nats-jetstream-errors.formatter.d.ts} +1 -1
- package/dist/{nats-jetstream-errors.formatter.js → jetstream/nats-jetstream-errors.formatter.js} +2 -1
- package/dist/jetstream/nats-jetstream-errors.formatter.js.map +1 -0
- package/dist/jetstream/nats-jetstream-transport.module.js.map +1 -0
- package/dist/{nats-jetstream.config.d.ts → jetstream/nats-jetstream.config.d.ts} +1 -1
- package/dist/jetstream/nats-jetstream.config.js.map +1 -0
- package/dist/jetstream/nats-jetstream.headers.d.ts +10 -0
- package/dist/{nats.headers.js → jetstream/nats-jetstream.headers.js} +4 -4
- package/dist/jetstream/nats-jetstream.headers.js.map +1 -0
- package/dist/{nats-jetstream.transport.d.ts → jetstream/nats-jetstream.transport.d.ts} +1 -1
- package/dist/{nats-jetstream.transport.js → jetstream/nats-jetstream.transport.js} +5 -4
- package/dist/jetstream/nats-jetstream.transport.js.map +1 -0
- package/dist/jetstream/payload.decorator.js.map +1 -0
- package/dist/{server.d.ts → jetstream/server.d.ts} +12 -17
- package/dist/{server.js → jetstream/server.js} +38 -77
- package/dist/jetstream/server.js.map +1 -0
- package/dist/jetstream/types/consume.options.js.map +1 -0
- package/dist/jetstream/types/consumer-info.type.js.map +1 -0
- package/dist/jetstream/types/consumers-map.type.js.map +1 -0
- package/dist/{types → jetstream/types}/event-type-with-options.type.d.ts +1 -1
- package/dist/jetstream/types/event-type-with-options.type.js.map +1 -0
- package/dist/jetstream/types/index.d.ts +6 -0
- package/dist/jetstream/types/index.js +23 -0
- package/dist/jetstream/types/index.js.map +1 -0
- package/dist/jetstream/types/mapping.type.js.map +1 -0
- package/dist/jetstream/types/nats-jetstream-transport-options.type.js.map +1 -0
- package/dist/request/constants.d.ts +1 -0
- package/dist/request/constants.js +5 -0
- package/dist/request/constants.js.map +1 -0
- package/dist/request/consume-request.decorator.d.ts +3 -0
- package/dist/request/consume-request.decorator.js +10 -0
- package/dist/request/consume-request.decorator.js.map +1 -0
- package/dist/request/index.d.ts +4 -0
- package/dist/request/index.js +21 -0
- package/dist/request/index.js.map +1 -0
- package/dist/request/nats-request-errors.formatter.d.ts +7 -0
- package/dist/request/nats-request-errors.formatter.js +41 -0
- package/dist/request/nats-request-errors.formatter.js.map +1 -0
- package/dist/request/nats-request-errors.sender.d.ts +8 -0
- package/dist/request/nats-request-errors.sender.js +28 -0
- package/dist/request/nats-request-errors.sender.js.map +1 -0
- package/dist/request/nats-request-logger.interceptor.d.ts +13 -0
- package/dist/request/nats-request-logger.interceptor.js +89 -0
- package/dist/request/nats-request-logger.interceptor.js.map +1 -0
- package/dist/request/nats-request-transport.module.d.ts +2 -0
- package/dist/request/nats-request-transport.module.js +17 -0
- package/dist/request/nats-request-transport.module.js.map +1 -0
- package/dist/request/nats-request.config.d.ts +8 -0
- package/dist/request/nats-request.config.js +46 -0
- package/dist/request/nats-request.config.js.map +1 -0
- package/dist/{nats.headers.d.ts → request/nats-request.headers.d.ts} +1 -1
- package/dist/request/nats-request.headers.js +28 -0
- package/dist/request/nats-request.headers.js.map +1 -0
- package/dist/request/nats-request.transport.d.ts +22 -0
- package/dist/request/nats-request.transport.js +83 -0
- package/dist/request/nats-request.transport.js.map +1 -0
- package/dist/request/server.d.ts +59 -0
- package/dist/request/server.js +174 -0
- package/dist/request/server.js.map +1 -0
- package/dist/request/types/consume-request-options.type.d.ts +4 -0
- package/dist/request/types/consume-request-options.type.js +3 -0
- package/dist/request/types/consume-request-options.type.js.map +1 -0
- package/dist/request/types/formatted-nats-error.type.d.ts +8 -0
- package/dist/request/types/formatted-nats-error.type.js +3 -0
- package/dist/request/types/formatted-nats-error.type.js.map +1 -0
- package/dist/request/types/index.d.ts +3 -0
- package/dist/request/types/index.js +20 -0
- package/dist/request/types/index.js.map +1 -0
- package/dist/request/types/request-type-with-options.type.d.ts +6 -0
- package/dist/request/types/request-type-with-options.type.js +3 -0
- package/dist/request/types/request-type-with-options.type.js.map +1 -0
- package/package.json +6 -6
- package/src/events.deserializer.ts +8 -7
- package/src/index.ts +13 -5
- package/src/jetstream/constants.ts +6 -0
- package/src/{metadata.decorator.ts → jetstream/consume.decorator.ts} +2 -2
- package/src/jetstream/index.ts +7 -0
- package/src/{nats-jetstream-errors.formatter.ts → jetstream/nats-jetstream-errors.formatter.ts} +4 -2
- package/src/{nats-jetstream.config.ts → jetstream/nats-jetstream.config.ts} +1 -1
- package/src/{nats.headers.ts → jetstream/nats-jetstream.headers.ts} +1 -1
- package/src/{nats-jetstream.transport.ts → jetstream/nats-jetstream.transport.ts} +6 -5
- package/src/{server.ts → jetstream/server.ts} +65 -103
- package/src/{types → jetstream/types}/event-type-with-options.type.ts +1 -1
- package/src/jetstream/types/index.ts +6 -0
- package/src/{types → jetstream/types}/mapping.type.ts +4 -1
- package/src/request/constants.ts +1 -0
- package/src/request/consume-request.decorator.ts +18 -0
- package/src/request/index.ts +4 -0
- package/src/request/nats-request-errors.formatter.ts +49 -0
- package/src/request/nats-request-errors.sender.ts +39 -0
- package/src/request/nats-request-logger.interceptor.ts +89 -0
- package/src/request/nats-request-transport.module.ts +4 -0
- package/src/request/nats-request.config.ts +36 -0
- package/src/request/nats-request.headers.ts +33 -0
- package/src/request/nats-request.transport.ts +107 -0
- package/src/request/server.ts +260 -0
- package/src/request/types/consume-request-options.type.ts +5 -0
- package/src/request/types/formatted-nats-error.type.ts +9 -0
- package/src/request/types/index.ts +3 -0
- package/src/request/types/request-type-with-options.type.ts +8 -0
- package/dist/metadata.decorator.js.map +0 -1
- package/dist/nats-jetstream-errors.formatter.js.map +0 -1
- package/dist/nats-jetstream-transport.module.js.map +0 -1
- package/dist/nats-jetstream.config.js.map +0 -1
- package/dist/nats-jetstream.transport.js.map +0 -1
- package/dist/nats.headers.js.map +0 -1
- package/dist/payload.decorator.js.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/types/consume.options.js.map +0 -1
- package/dist/types/consumer-info.type.js.map +0 -1
- package/dist/types/consumers-map.type.js.map +0 -1
- package/dist/types/event-type-with-options.type.js.map +0 -1
- package/dist/types/mapping.type.js.map +0 -1
- package/dist/types/nats-jetstream-transport-options.type.js.map +0 -1
- /package/dist/{nats-jetstream-transport.module.d.ts → jetstream/nats-jetstream-transport.module.d.ts} +0 -0
- /package/dist/{nats-jetstream-transport.module.js → jetstream/nats-jetstream-transport.module.js} +0 -0
- /package/dist/{nats-jetstream.config.js → jetstream/nats-jetstream.config.js} +0 -0
- /package/dist/{payload.decorator.d.ts → jetstream/payload.decorator.d.ts} +0 -0
- /package/dist/{payload.decorator.js → jetstream/payload.decorator.js} +0 -0
- /package/dist/{types → jetstream/types}/consume.options.d.ts +0 -0
- /package/dist/{types → jetstream/types}/consume.options.js +0 -0
- /package/dist/{types → jetstream/types}/consumer-info.type.d.ts +0 -0
- /package/dist/{types → jetstream/types}/consumer-info.type.js +0 -0
- /package/dist/{types → jetstream/types}/consumers-map.type.d.ts +0 -0
- /package/dist/{types → jetstream/types}/consumers-map.type.js +0 -0
- /package/dist/{types → jetstream/types}/event-type-with-options.type.js +0 -0
- /package/dist/{types → jetstream/types}/mapping.type.d.ts +0 -0
- /package/dist/{types → jetstream/types}/mapping.type.js +0 -0
- /package/dist/{types → jetstream/types}/nats-jetstream-transport-options.type.d.ts +0 -0
- /package/dist/{types → jetstream/types}/nats-jetstream-transport-options.type.js +0 -0
- /package/src/{nats-jetstream-transport.module.ts → jetstream/nats-jetstream-transport.module.ts} +0 -0
- /package/src/{payload.decorator.ts → jetstream/payload.decorator.ts} +0 -0
- /package/src/{types → jetstream/types}/consume.options.ts +0 -0
- /package/src/{types → jetstream/types}/consumer-info.type.ts +0 -0
- /package/src/{types → jetstream/types}/consumers-map.type.ts +0 -0
- /package/src/{types → jetstream/types}/nats-jetstream-transport-options.type.ts +0 -0
|
@@ -14,7 +14,7 @@ import { InternalException, NotFoundException } from '@rsdk/core';
|
|
|
14
14
|
import type { EventType } from '@rsdk/events.common';
|
|
15
15
|
import { isEventType, X_TYPE_HEADER } from '@rsdk/events.common';
|
|
16
16
|
import { LoggerFactory } from '@rsdk/logging';
|
|
17
|
-
import { getStreamName } from '@rsdk/nats.common';
|
|
17
|
+
import { getStreamName, isMessageType } from '@rsdk/nats.common';
|
|
18
18
|
import { isEqual } from 'lodash';
|
|
19
19
|
import type {
|
|
20
20
|
Consumer,
|
|
@@ -22,16 +22,18 @@ import type {
|
|
|
22
22
|
JetStreamClient,
|
|
23
23
|
JetStreamManager,
|
|
24
24
|
NatsConnection,
|
|
25
|
-
SubscriptionOptions,
|
|
26
25
|
} from 'nats';
|
|
27
26
|
import { connect } from 'nats';
|
|
28
27
|
import { connectable, isObservable, Subject } from 'rxjs';
|
|
29
28
|
|
|
30
|
-
import
|
|
31
|
-
|
|
32
|
-
import type {
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
import { EventsDeserializer } from '../events.deserializer';
|
|
30
|
+
|
|
31
|
+
import type {
|
|
32
|
+
ConsumerInfo,
|
|
33
|
+
ConsumersMap,
|
|
34
|
+
EventTypeWithOptions,
|
|
35
|
+
Mapping,
|
|
36
|
+
} from './types';
|
|
35
37
|
|
|
36
38
|
/**
|
|
37
39
|
* Класс, реализующий сервер NATS JetStream.
|
|
@@ -41,8 +43,10 @@ export class NatsJetStreamServer
|
|
|
41
43
|
implements CustomTransportStrategy
|
|
42
44
|
{
|
|
43
45
|
readonly transportId = NATS_JETSTREAM_TRANSPORT;
|
|
44
|
-
override logger = LoggerFactory.create(
|
|
45
|
-
override readonly deserializer =
|
|
46
|
+
override logger = LoggerFactory.create(NatsJetStreamServer.name);
|
|
47
|
+
override readonly deserializer =
|
|
48
|
+
new EventsDeserializer<EventTypeWithOptions>();
|
|
49
|
+
|
|
46
50
|
private nc?: NatsConnection;
|
|
47
51
|
private jsm?: JetStreamManager;
|
|
48
52
|
private readonly streams = new Set<string>();
|
|
@@ -69,8 +73,11 @@ export class NatsJetStreamServer
|
|
|
69
73
|
|
|
70
74
|
static isNatsContext(
|
|
71
75
|
maybeNatsContext: unknown,
|
|
72
|
-
): maybeNatsContext is NatsJetStreamContext {
|
|
73
|
-
return
|
|
76
|
+
): maybeNatsContext is NatsJetStreamContext | NatsContext {
|
|
77
|
+
return (
|
|
78
|
+
maybeNatsContext instanceof NatsJetStreamContext ||
|
|
79
|
+
maybeNatsContext instanceof NatsContext
|
|
80
|
+
);
|
|
74
81
|
}
|
|
75
82
|
|
|
76
83
|
/**
|
|
@@ -85,7 +92,6 @@ export class NatsJetStreamServer
|
|
|
85
92
|
this.jsm = await this.nc.jetstreamManager(this.options.jetStreamOptions);
|
|
86
93
|
|
|
87
94
|
await this.bindEventHandlers();
|
|
88
|
-
this.bindMessageHandlers();
|
|
89
95
|
callback();
|
|
90
96
|
}
|
|
91
97
|
|
|
@@ -110,29 +116,57 @@ export class NatsJetStreamServer
|
|
|
110
116
|
isEventHandler = false,
|
|
111
117
|
extras: Record<string, any> = {},
|
|
112
118
|
): void {
|
|
113
|
-
|
|
114
|
-
|
|
119
|
+
if (isEventHandler) {
|
|
120
|
+
return this.addEventHandler(pattern, callback, extras);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
throw new InternalException(
|
|
124
|
+
'Request/reply handlers are not supported for this transport',
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private addEventHandler(
|
|
129
|
+
pattern: any,
|
|
130
|
+
callback: MessageHandler,
|
|
131
|
+
extras: Record<string, any> = {},
|
|
132
|
+
): void {
|
|
133
|
+
const eventType = this.getMessageType(pattern);
|
|
134
|
+
const formattedPattern = this.getFormattedPattern(eventType);
|
|
135
|
+
|
|
136
|
+
const stream = isEventType(eventType)
|
|
137
|
+
? this.getStreamName(pattern)
|
|
138
|
+
: eventType;
|
|
115
139
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
140
|
+
this.streams.add(stream);
|
|
141
|
+
this.deserializer.events.set(formattedPattern, pattern);
|
|
142
|
+
|
|
143
|
+
super.addHandler(formattedPattern, callback, true, extras);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private getFormattedPattern(eventType: any): string {
|
|
147
|
+
if (isMessageType(eventType)) {
|
|
148
|
+
return eventType.$type;
|
|
120
149
|
} else if (typeof eventType === 'string') {
|
|
121
|
-
|
|
122
|
-
|
|
150
|
+
/**
|
|
151
|
+
* NOTE: сейчас если указывается строка, то мы исключаем decode формата protobuf.
|
|
152
|
+
* Наверное стоит разделить subject и декодеры на разные поля.
|
|
153
|
+
*/
|
|
154
|
+
return eventType;
|
|
123
155
|
}
|
|
124
156
|
|
|
125
|
-
|
|
157
|
+
throw new InternalException(
|
|
158
|
+
'Invalid pattern type: expected event type or string',
|
|
159
|
+
);
|
|
126
160
|
}
|
|
127
161
|
|
|
128
162
|
/**
|
|
129
|
-
* Получает тип
|
|
130
|
-
* @param pattern - Шаблон для получения типа
|
|
131
|
-
* @returns Тип
|
|
163
|
+
* Получает тип сообщения из шаблона.
|
|
164
|
+
* @param pattern - Шаблон для получения типа сообщения.
|
|
165
|
+
* @returns Тип сообщения.
|
|
132
166
|
*/
|
|
133
|
-
private
|
|
134
|
-
return typeof pattern === 'object' && '
|
|
135
|
-
? pattern.
|
|
167
|
+
private getMessageType(pattern: any): any {
|
|
168
|
+
return typeof pattern === 'object' && 'type' in pattern
|
|
169
|
+
? pattern.type
|
|
136
170
|
: pattern;
|
|
137
171
|
}
|
|
138
172
|
|
|
@@ -197,7 +231,7 @@ export class NatsJetStreamServer
|
|
|
197
231
|
mapping: new Map() as Mapping,
|
|
198
232
|
};
|
|
199
233
|
|
|
200
|
-
aggregatedConsumer.mapping.set(this.getTypePart(deserializer.
|
|
234
|
+
aggregatedConsumer.mapping.set(this.getTypePart(deserializer.type), {
|
|
201
235
|
deserializer,
|
|
202
236
|
handler,
|
|
203
237
|
});
|
|
@@ -278,7 +312,7 @@ export class NatsJetStreamServer
|
|
|
278
312
|
receivedSubjects,
|
|
279
313
|
);
|
|
280
314
|
|
|
281
|
-
this.
|
|
315
|
+
this.handleEventMessages(consumer, mapping, consumerName);
|
|
282
316
|
}
|
|
283
317
|
|
|
284
318
|
/**
|
|
@@ -287,7 +321,7 @@ export class NatsJetStreamServer
|
|
|
287
321
|
* @param mapping - Соответствия между типами сообщений и обработчиками.
|
|
288
322
|
* @param consumerName - Имя консьюмера.
|
|
289
323
|
*/
|
|
290
|
-
private async
|
|
324
|
+
private async handleEventMessages(
|
|
291
325
|
consumer: Consumer,
|
|
292
326
|
mapping: Mapping,
|
|
293
327
|
consumerName: string,
|
|
@@ -368,85 +402,13 @@ export class NatsJetStreamServer
|
|
|
368
402
|
});
|
|
369
403
|
}
|
|
370
404
|
|
|
371
|
-
/**
|
|
372
|
-
* Связывает обработчики сообщений.
|
|
373
|
-
*/
|
|
374
|
-
private bindMessageHandlers(): void {
|
|
375
|
-
const messageHandlers = [...this.messageHandlers.entries()].filter(
|
|
376
|
-
([, handler]) => !handler.isEventHandler,
|
|
377
|
-
);
|
|
378
|
-
|
|
379
|
-
for (const [subject, messageHandler] of messageHandlers) {
|
|
380
|
-
const subscriptionOptions: SubscriptionOptions = {
|
|
381
|
-
callback: async (err, msg) => {
|
|
382
|
-
if (err) {
|
|
383
|
-
this.logger.error(err.message, err);
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
const payload = this.deserializer.deserialize(msg);
|
|
387
|
-
const context = new NatsContext([msg]);
|
|
388
|
-
const response$ = this.transformToObservable(
|
|
389
|
-
messageHandler(payload.data, context),
|
|
390
|
-
);
|
|
391
|
-
|
|
392
|
-
this.send(response$, (response) =>
|
|
393
|
-
msg.respond(response as Uint8Array),
|
|
394
|
-
);
|
|
395
|
-
},
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
this.nc?.subscribe(subject, subscriptionOptions);
|
|
399
|
-
this.logger.debug(`Subscribed to ${subject} messages`);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Настраивает поток для сервера.
|
|
405
|
-
*/
|
|
406
|
-
private async setupStream(): Promise<void> {
|
|
407
|
-
const { streamConfig } = this.options;
|
|
408
|
-
const streams = await this.jsm?.streams.list().next();
|
|
409
|
-
|
|
410
|
-
const reqStreamConfigs = Array.isArray(streamConfig)
|
|
411
|
-
? streamConfig
|
|
412
|
-
: [streamConfig];
|
|
413
|
-
|
|
414
|
-
for (const requiredStreamConfig of reqStreamConfigs) {
|
|
415
|
-
if (!requiredStreamConfig) {
|
|
416
|
-
continue;
|
|
417
|
-
}
|
|
418
|
-
const stream = streams?.find(
|
|
419
|
-
(stream) => stream.config.name === requiredStreamConfig?.name,
|
|
420
|
-
);
|
|
421
|
-
|
|
422
|
-
if (stream) {
|
|
423
|
-
const streamSubjects = new Set([
|
|
424
|
-
...stream.config.subjects,
|
|
425
|
-
...(requiredStreamConfig?.subjects ?? []),
|
|
426
|
-
]);
|
|
427
|
-
|
|
428
|
-
const streamInfo = await this.jsm?.streams.update(stream.config.name, {
|
|
429
|
-
...stream.config,
|
|
430
|
-
...requiredStreamConfig,
|
|
431
|
-
subjects: [...streamSubjects.keys()],
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
this.logger.info(`Stream ${streamInfo?.config.name} updated`);
|
|
435
|
-
} else {
|
|
436
|
-
const streamInfo = await this.jsm?.streams.add(requiredStreamConfig);
|
|
437
|
-
|
|
438
|
-
this.logger.info(`Stream ${streamInfo?.config.name} created`);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
405
|
/**
|
|
444
406
|
* Получает имя потока для десериализатора.
|
|
445
407
|
* @param contract - Десериализатор событий.
|
|
446
408
|
* @returns Имя потока.
|
|
447
409
|
*/
|
|
448
410
|
private getStreamName(contract: EventTypeWithOptions): string {
|
|
449
|
-
return contract.options?.streamName ?? getStreamName(contract.
|
|
411
|
+
return contract.options?.streamName ?? getStreamName(contract.type);
|
|
450
412
|
}
|
|
451
413
|
|
|
452
414
|
/**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const NATS_REQUEST_PROTOCOL = 'nats-request';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { applyDecorators } from '@nestjs/common';
|
|
2
|
+
import { MessagePattern, Transport } from '@nestjs/microservices';
|
|
3
|
+
import type { MessageType } from '@rsdk/nats.common';
|
|
4
|
+
|
|
5
|
+
import type { ConsumeRequestOptions } from './types';
|
|
6
|
+
|
|
7
|
+
export const ConsumeRequest = <T>(
|
|
8
|
+
request: MessageType<T>,
|
|
9
|
+
response: MessageType<T>,
|
|
10
|
+
options?: Omit<ConsumeRequestOptions, 'response'>,
|
|
11
|
+
): MethodDecorator => {
|
|
12
|
+
return applyDecorators(
|
|
13
|
+
MessagePattern(
|
|
14
|
+
{ type: request, options: { ...options, response } },
|
|
15
|
+
Transport.NATS,
|
|
16
|
+
),
|
|
17
|
+
);
|
|
18
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ExceptionKind,
|
|
3
|
+
type IErrorsFormatter,
|
|
4
|
+
PipelineException,
|
|
5
|
+
} from '@rsdk/core';
|
|
6
|
+
|
|
7
|
+
import { NATS_REQUEST_PROTOCOL } from './constants';
|
|
8
|
+
|
|
9
|
+
export class NatsRequestErrorsFormatter implements IErrorsFormatter {
|
|
10
|
+
static readonly defaultKind = ExceptionKind.UNKNOWN;
|
|
11
|
+
protocol = NATS_REQUEST_PROTOCOL;
|
|
12
|
+
|
|
13
|
+
match(): boolean {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
format(ex: unknown, verboseErrors?: boolean): unknown {
|
|
18
|
+
if (ex instanceof PipelineException) {
|
|
19
|
+
const message = verboseErrors
|
|
20
|
+
? [
|
|
21
|
+
ex.message,
|
|
22
|
+
...PipelineException.innerMessages(ex).map((msg) =>
|
|
23
|
+
msg.replace(ex.message, '').trim().replace(/:$/, ''),
|
|
24
|
+
),
|
|
25
|
+
].join(': ')
|
|
26
|
+
: ExceptionKind[ex.kind].toString();
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
code: ex.code,
|
|
30
|
+
message,
|
|
31
|
+
details: ex.details || {},
|
|
32
|
+
kind: ex.kind,
|
|
33
|
+
exceptionCode: ex.code.toString(),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const defaultExMessage = 'UNKNOWN ERROR';
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
code: 'UNKNOWN',
|
|
41
|
+
message: verboseErrors
|
|
42
|
+
? ((ex as any)?.message ?? defaultExMessage)
|
|
43
|
+
: defaultExMessage,
|
|
44
|
+
details: (ex as any)?.details ?? {},
|
|
45
|
+
kind: (ex as any)?.kind ?? NatsRequestErrorsFormatter.defaultKind,
|
|
46
|
+
exceptionCode: (ex as any)?.code?.toString(),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ArgumentsHost } from '@nestjs/common';
|
|
2
|
+
import { Status } from '@rsdk/builtin-contract/dist/grpc.error.v1';
|
|
3
|
+
import type { IErrorsSender } from '@rsdk/core';
|
|
4
|
+
import { MsgHdrsImpl } from 'nats';
|
|
5
|
+
import type { EMPTY, Observable } from 'rxjs';
|
|
6
|
+
import { throwError } from 'rxjs';
|
|
7
|
+
|
|
8
|
+
import { NATS_REQUEST_PROTOCOL } from './constants';
|
|
9
|
+
import type { FormattedNatsError } from './types';
|
|
10
|
+
|
|
11
|
+
export class NatsRequestErrorsSender implements IErrorsSender {
|
|
12
|
+
protocol = NATS_REQUEST_PROTOCOL;
|
|
13
|
+
|
|
14
|
+
send(
|
|
15
|
+
_host: ArgumentsHost,
|
|
16
|
+
ex: FormattedNatsError,
|
|
17
|
+
): Observable<any> | typeof EMPTY {
|
|
18
|
+
const metadata = new MsgHdrsImpl();
|
|
19
|
+
|
|
20
|
+
const status = Status.fromPartial({
|
|
21
|
+
code: ex.kind,
|
|
22
|
+
details: ex.details,
|
|
23
|
+
message: ex.message,
|
|
24
|
+
exceptionCode: ex.exceptionCode,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
metadata.set(
|
|
28
|
+
'nats-status-details-hex',
|
|
29
|
+
(Status.encode(status).finish() as Buffer).toString('hex'),
|
|
30
|
+
);
|
|
31
|
+
const error = {
|
|
32
|
+
code: ex.code,
|
|
33
|
+
details: ex.message,
|
|
34
|
+
metadata,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return throwError(() => error);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CallHandler,
|
|
3
|
+
ExecutionContext,
|
|
4
|
+
NestInterceptor,
|
|
5
|
+
} from '@nestjs/common/interfaces';
|
|
6
|
+
import { InjectLogger, ProtocolDetector } from '@rsdk/core';
|
|
7
|
+
import { ILogger } from '@rsdk/logging';
|
|
8
|
+
import type { Observable } from 'rxjs';
|
|
9
|
+
import { catchError, tap, throwError } from 'rxjs';
|
|
10
|
+
|
|
11
|
+
import { NATS_REQUEST_PROTOCOL } from './constants';
|
|
12
|
+
import { NatsRequestConfig } from './nats-request.config';
|
|
13
|
+
|
|
14
|
+
export class NatsRequestLoggerInterceptor implements NestInterceptor {
|
|
15
|
+
constructor(
|
|
16
|
+
@InjectLogger(NatsRequestLoggerInterceptor) private logger: ILogger,
|
|
17
|
+
private config: NatsRequestConfig,
|
|
18
|
+
private detector: ProtocolDetector,
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
intercept(
|
|
22
|
+
context: ExecutionContext,
|
|
23
|
+
next: CallHandler<any>,
|
|
24
|
+
): Observable<any> | Promise<Observable<any>> {
|
|
25
|
+
if (!this.config.requestLogging) {
|
|
26
|
+
return next.handle();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const protocol = this.detector.getProtocol(context);
|
|
30
|
+
|
|
31
|
+
if (protocol !== NATS_REQUEST_PROTOCOL) {
|
|
32
|
+
return next.handle();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const rpc = context.switchToRpc();
|
|
36
|
+
|
|
37
|
+
const startDate = Date.now();
|
|
38
|
+
const rpcContext = rpc.getContext();
|
|
39
|
+
const serializedCtx = rpcContext?.toJSON?.() ?? rpcContext;
|
|
40
|
+
const requestForLog = {
|
|
41
|
+
data: rpc.getData(),
|
|
42
|
+
ctx: serializedCtx,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const path = this.getRpcPath(context);
|
|
46
|
+
|
|
47
|
+
this.logger.trace(`received nats request ${path}`, {
|
|
48
|
+
request: requestForLog,
|
|
49
|
+
timestamp: startDate,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return next.handle().pipe(
|
|
53
|
+
catchError((error) => {
|
|
54
|
+
const endDate = Date.now();
|
|
55
|
+
|
|
56
|
+
this.logger.trace(`handled nats request ${path} with error`, {
|
|
57
|
+
request: requestForLog,
|
|
58
|
+
startDate,
|
|
59
|
+
endDate,
|
|
60
|
+
duration: endDate - startDate,
|
|
61
|
+
error,
|
|
62
|
+
});
|
|
63
|
+
return throwError(() => error);
|
|
64
|
+
}),
|
|
65
|
+
tap((response) => {
|
|
66
|
+
const endDate = Date.now();
|
|
67
|
+
|
|
68
|
+
this.logger.trace(`handled nats request ${path}`, {
|
|
69
|
+
request: requestForLog,
|
|
70
|
+
startDate,
|
|
71
|
+
endDate,
|
|
72
|
+
duration: endDate - startDate,
|
|
73
|
+
response,
|
|
74
|
+
});
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private getRpcPath(context: ExecutionContext): string | undefined {
|
|
80
|
+
try {
|
|
81
|
+
return context.getArgs()[1]?.args[0];
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (error instanceof Error) {
|
|
84
|
+
this.logger.warn(error.message, { error });
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BoolParser,
|
|
3
|
+
Config,
|
|
4
|
+
ConfigSection,
|
|
5
|
+
EnumParser,
|
|
6
|
+
Property,
|
|
7
|
+
StringParser,
|
|
8
|
+
} from '@rsdk/core';
|
|
9
|
+
import { DEFAULT_PAYLOAD_FORMAT, PayloadFormat } from '@rsdk/events.common';
|
|
10
|
+
|
|
11
|
+
export const NatsRequestConfigInstance = Symbol('NatsRequestConfigInstance');
|
|
12
|
+
|
|
13
|
+
@ConfigSection()
|
|
14
|
+
export class NatsRequestConfig extends Config {
|
|
15
|
+
@Property('NATS_REQUEST_NAME', new StringParser(), {
|
|
16
|
+
defaultValue: 'default',
|
|
17
|
+
description: 'NATS connection name',
|
|
18
|
+
})
|
|
19
|
+
readonly name!: string;
|
|
20
|
+
|
|
21
|
+
@Property(
|
|
22
|
+
'NATS_DEFAULT_RESPONSE_PAYLOAD_TYPE',
|
|
23
|
+
new EnumParser(PayloadFormat),
|
|
24
|
+
{
|
|
25
|
+
defaultValue: DEFAULT_PAYLOAD_FORMAT,
|
|
26
|
+
description: `Default payload type of response. Possible values: ${Object.values(PayloadFormat).join(', ')}.`,
|
|
27
|
+
},
|
|
28
|
+
)
|
|
29
|
+
readonly defaultResponsePayloadType!: PayloadFormat;
|
|
30
|
+
|
|
31
|
+
@Property('NATS_REQUEST_LOGGING', new BoolParser(), {
|
|
32
|
+
defaultValue: false,
|
|
33
|
+
description: 'Enable trace logging all requests',
|
|
34
|
+
})
|
|
35
|
+
readonly requestLogging!: boolean;
|
|
36
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ExecutionContext } from '@nestjs/common';
|
|
2
|
+
import type { GenericHeaders } from '@rsdk/core';
|
|
3
|
+
import { InputException } from '@rsdk/core';
|
|
4
|
+
|
|
5
|
+
import { NatsRequestServer } from './server';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @description хелпер для нормализованного извлечения заголовков из nats
|
|
9
|
+
*/
|
|
10
|
+
export class NatsRequestHeaders implements GenericHeaders {
|
|
11
|
+
private readonly normalizedHeaders: Record<string, string>;
|
|
12
|
+
|
|
13
|
+
constructor(ctx: ExecutionContext) {
|
|
14
|
+
const natsCtx = ctx.getArgByIndex(1);
|
|
15
|
+
|
|
16
|
+
if (!NatsRequestServer.isNatsContext(natsCtx)) {
|
|
17
|
+
throw new InputException('Unexpected call with non Nats ArgumentHost');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
this.normalizedHeaders = Object.fromEntries(
|
|
21
|
+
Object.entries(natsCtx.getHeaders() || {}).map(([k, v]) => [
|
|
22
|
+
Array.isArray(k)
|
|
23
|
+
? k[0].toString().toLowerCase()
|
|
24
|
+
: k.toString().toLowerCase(),
|
|
25
|
+
v,
|
|
26
|
+
]),
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get(key: string): string | undefined {
|
|
31
|
+
return this.normalizedHeaders[key.toLowerCase()];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { ArgumentsHost, ExecutionContext } from '@nestjs/common';
|
|
2
|
+
import { APP_INTERCEPTOR } from '@nestjs/core';
|
|
3
|
+
import type { CustomStrategy } from '@nestjs/microservices';
|
|
4
|
+
import {
|
|
5
|
+
type ConfigContext,
|
|
6
|
+
type GenericHeaders,
|
|
7
|
+
type IErrorsFormatter,
|
|
8
|
+
type IErrorsSender,
|
|
9
|
+
type IErrorsTransformer,
|
|
10
|
+
type IMicroserviceTransport,
|
|
11
|
+
Manifest,
|
|
12
|
+
type NestModuleDefinitions,
|
|
13
|
+
} from '@rsdk/core';
|
|
14
|
+
import { PayloadFormat } from '@rsdk/events.common';
|
|
15
|
+
import { LoggerFactory } from '@rsdk/logging';
|
|
16
|
+
import { DefaultNatsConfig } from '@rsdk/nats.common';
|
|
17
|
+
import type { ConnectionOptions } from 'nats';
|
|
18
|
+
|
|
19
|
+
import { NATS_REQUEST_PROTOCOL } from './constants';
|
|
20
|
+
import { NatsRequestConfig } from './nats-request.config';
|
|
21
|
+
import { NatsRequestHeaders } from './nats-request.headers';
|
|
22
|
+
import { NatsRequestErrorsFormatter } from './nats-request-errors.formatter';
|
|
23
|
+
import { NatsRequestErrorsSender } from './nats-request-errors.sender';
|
|
24
|
+
import { NatsRequestLoggerInterceptor } from './nats-request-logger.interceptor';
|
|
25
|
+
import { NatsRequestTransportModule } from './nats-request-transport.module';
|
|
26
|
+
import { NatsRequestServer } from './server';
|
|
27
|
+
|
|
28
|
+
export class NatsRequestTransport implements IMicroserviceTransport {
|
|
29
|
+
private readonly logger = LoggerFactory.create(NatsRequestTransport);
|
|
30
|
+
private config: DefaultNatsConfig | null = null;
|
|
31
|
+
private name = 'nats-request-unknown';
|
|
32
|
+
private responsePayloadType: PayloadFormat = PayloadFormat.JSON;
|
|
33
|
+
|
|
34
|
+
constructor(private readonly options?: ConnectionOptions) {}
|
|
35
|
+
|
|
36
|
+
extractHeaders(ctx: ExecutionContext): GenericHeaders {
|
|
37
|
+
return new NatsRequestHeaders(ctx);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getProtocol(): string {
|
|
41
|
+
return NATS_REQUEST_PROTOCOL;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
matchByContext(ctx: ArgumentsHost): boolean {
|
|
45
|
+
const natsCtx = ctx.getArgByIndex(1);
|
|
46
|
+
|
|
47
|
+
return NatsRequestServer.isNatsContext(natsCtx);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getErrorsFormatter(): IErrorsFormatter {
|
|
51
|
+
return new NatsRequestErrorsFormatter();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getErrorsSender(): IErrorsSender {
|
|
55
|
+
return new NatsRequestErrorsSender();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getErrorTransformers(): IErrorsTransformer[] {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
modules(): NestModuleDefinitions {
|
|
63
|
+
return [
|
|
64
|
+
{
|
|
65
|
+
module: NatsRequestTransportModule,
|
|
66
|
+
providers: [
|
|
67
|
+
{ provide: APP_INTERCEPTOR, useClass: NatsRequestLoggerInterceptor },
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
createMicroserviceOptions(): CustomStrategy {
|
|
74
|
+
const options: ConnectionOptions = {
|
|
75
|
+
...this.options,
|
|
76
|
+
name: this.name,
|
|
77
|
+
servers: this.config?.servers ?? 'unknown',
|
|
78
|
+
maxReconnectAttempts: this.config?.maxReconnectAttempts ?? -1,
|
|
79
|
+
reconnectTimeWait: this.config?.reconnectTimeWait ?? 2000,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
this.logger.trace('createMicroserviceOptions', { options });
|
|
83
|
+
|
|
84
|
+
const { name } = Manifest.getData();
|
|
85
|
+
const server = new NatsRequestServer(options, {
|
|
86
|
+
appName: name,
|
|
87
|
+
responsePayloadType: this.responsePayloadType,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return { strategy: server };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
init(configContext: ConfigContext): void {
|
|
94
|
+
const natsConfig = configContext.resolve(DefaultNatsConfig);
|
|
95
|
+
const natsRequestConfig = configContext.resolve(NatsRequestConfig);
|
|
96
|
+
|
|
97
|
+
this.config = natsConfig;
|
|
98
|
+
this.name = natsRequestConfig.name;
|
|
99
|
+
this.responsePayloadType = natsRequestConfig.defaultResponsePayloadType;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
onStart(): void {
|
|
103
|
+
this.logger.info(
|
|
104
|
+
`Nats request servers: ${this.config?.servers?.join(',') ?? 'unknown'}`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|