@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.
Files changed (151) hide show
  1. package/dist/events.deserializer.d.ts +4 -3
  2. package/dist/events.deserializer.js +2 -2
  3. package/dist/events.deserializer.js.map +1 -1
  4. package/dist/index.d.ts +2 -4
  5. package/dist/index.js +11 -7
  6. package/dist/index.js.map +1 -1
  7. package/dist/jetstream/constants.d.ts +5 -0
  8. package/dist/jetstream/constants.js +9 -0
  9. package/dist/jetstream/constants.js.map +1 -0
  10. package/dist/{metadata.decorator.d.ts → jetstream/consume.decorator.d.ts} +1 -1
  11. package/dist/{metadata.decorator.js → jetstream/consume.decorator.js} +2 -2
  12. package/dist/jetstream/consume.decorator.js.map +1 -0
  13. package/dist/jetstream/index.d.ts +7 -0
  14. package/dist/jetstream/index.js +24 -0
  15. package/dist/jetstream/index.js.map +1 -0
  16. package/dist/{nats-jetstream-errors.formatter.d.ts → jetstream/nats-jetstream-errors.formatter.d.ts} +1 -1
  17. package/dist/{nats-jetstream-errors.formatter.js → jetstream/nats-jetstream-errors.formatter.js} +2 -1
  18. package/dist/jetstream/nats-jetstream-errors.formatter.js.map +1 -0
  19. package/dist/jetstream/nats-jetstream-transport.module.js.map +1 -0
  20. package/dist/{nats-jetstream.config.d.ts → jetstream/nats-jetstream.config.d.ts} +1 -1
  21. package/dist/jetstream/nats-jetstream.config.js.map +1 -0
  22. package/dist/jetstream/nats-jetstream.headers.d.ts +10 -0
  23. package/dist/{nats.headers.js → jetstream/nats-jetstream.headers.js} +4 -4
  24. package/dist/jetstream/nats-jetstream.headers.js.map +1 -0
  25. package/dist/{nats-jetstream.transport.d.ts → jetstream/nats-jetstream.transport.d.ts} +1 -1
  26. package/dist/{nats-jetstream.transport.js → jetstream/nats-jetstream.transport.js} +5 -4
  27. package/dist/jetstream/nats-jetstream.transport.js.map +1 -0
  28. package/dist/jetstream/payload.decorator.js.map +1 -0
  29. package/dist/{server.d.ts → jetstream/server.d.ts} +12 -17
  30. package/dist/{server.js → jetstream/server.js} +38 -77
  31. package/dist/jetstream/server.js.map +1 -0
  32. package/dist/jetstream/types/consume.options.js.map +1 -0
  33. package/dist/jetstream/types/consumer-info.type.js.map +1 -0
  34. package/dist/jetstream/types/consumers-map.type.js.map +1 -0
  35. package/dist/{types → jetstream/types}/event-type-with-options.type.d.ts +1 -1
  36. package/dist/jetstream/types/event-type-with-options.type.js.map +1 -0
  37. package/dist/jetstream/types/index.d.ts +6 -0
  38. package/dist/jetstream/types/index.js +23 -0
  39. package/dist/jetstream/types/index.js.map +1 -0
  40. package/dist/jetstream/types/mapping.type.js.map +1 -0
  41. package/dist/jetstream/types/nats-jetstream-transport-options.type.js.map +1 -0
  42. package/dist/request/constants.d.ts +1 -0
  43. package/dist/request/constants.js +5 -0
  44. package/dist/request/constants.js.map +1 -0
  45. package/dist/request/consume-request.decorator.d.ts +3 -0
  46. package/dist/request/consume-request.decorator.js +10 -0
  47. package/dist/request/consume-request.decorator.js.map +1 -0
  48. package/dist/request/index.d.ts +4 -0
  49. package/dist/request/index.js +21 -0
  50. package/dist/request/index.js.map +1 -0
  51. package/dist/request/nats-request-errors.formatter.d.ts +7 -0
  52. package/dist/request/nats-request-errors.formatter.js +41 -0
  53. package/dist/request/nats-request-errors.formatter.js.map +1 -0
  54. package/dist/request/nats-request-errors.sender.d.ts +8 -0
  55. package/dist/request/nats-request-errors.sender.js +28 -0
  56. package/dist/request/nats-request-errors.sender.js.map +1 -0
  57. package/dist/request/nats-request-logger.interceptor.d.ts +13 -0
  58. package/dist/request/nats-request-logger.interceptor.js +89 -0
  59. package/dist/request/nats-request-logger.interceptor.js.map +1 -0
  60. package/dist/request/nats-request-transport.module.d.ts +2 -0
  61. package/dist/request/nats-request-transport.module.js +17 -0
  62. package/dist/request/nats-request-transport.module.js.map +1 -0
  63. package/dist/request/nats-request.config.d.ts +8 -0
  64. package/dist/request/nats-request.config.js +46 -0
  65. package/dist/request/nats-request.config.js.map +1 -0
  66. package/dist/{nats.headers.d.ts → request/nats-request.headers.d.ts} +1 -1
  67. package/dist/request/nats-request.headers.js +28 -0
  68. package/dist/request/nats-request.headers.js.map +1 -0
  69. package/dist/request/nats-request.transport.d.ts +22 -0
  70. package/dist/request/nats-request.transport.js +83 -0
  71. package/dist/request/nats-request.transport.js.map +1 -0
  72. package/dist/request/server.d.ts +59 -0
  73. package/dist/request/server.js +174 -0
  74. package/dist/request/server.js.map +1 -0
  75. package/dist/request/types/consume-request-options.type.d.ts +4 -0
  76. package/dist/request/types/consume-request-options.type.js +3 -0
  77. package/dist/request/types/consume-request-options.type.js.map +1 -0
  78. package/dist/request/types/formatted-nats-error.type.d.ts +8 -0
  79. package/dist/request/types/formatted-nats-error.type.js +3 -0
  80. package/dist/request/types/formatted-nats-error.type.js.map +1 -0
  81. package/dist/request/types/index.d.ts +3 -0
  82. package/dist/request/types/index.js +20 -0
  83. package/dist/request/types/index.js.map +1 -0
  84. package/dist/request/types/request-type-with-options.type.d.ts +6 -0
  85. package/dist/request/types/request-type-with-options.type.js +3 -0
  86. package/dist/request/types/request-type-with-options.type.js.map +1 -0
  87. package/package.json +6 -6
  88. package/src/events.deserializer.ts +8 -7
  89. package/src/index.ts +13 -5
  90. package/src/jetstream/constants.ts +6 -0
  91. package/src/{metadata.decorator.ts → jetstream/consume.decorator.ts} +2 -2
  92. package/src/jetstream/index.ts +7 -0
  93. package/src/{nats-jetstream-errors.formatter.ts → jetstream/nats-jetstream-errors.formatter.ts} +4 -2
  94. package/src/{nats-jetstream.config.ts → jetstream/nats-jetstream.config.ts} +1 -1
  95. package/src/{nats.headers.ts → jetstream/nats-jetstream.headers.ts} +1 -1
  96. package/src/{nats-jetstream.transport.ts → jetstream/nats-jetstream.transport.ts} +6 -5
  97. package/src/{server.ts → jetstream/server.ts} +65 -103
  98. package/src/{types → jetstream/types}/event-type-with-options.type.ts +1 -1
  99. package/src/jetstream/types/index.ts +6 -0
  100. package/src/{types → jetstream/types}/mapping.type.ts +4 -1
  101. package/src/request/constants.ts +1 -0
  102. package/src/request/consume-request.decorator.ts +18 -0
  103. package/src/request/index.ts +4 -0
  104. package/src/request/nats-request-errors.formatter.ts +49 -0
  105. package/src/request/nats-request-errors.sender.ts +39 -0
  106. package/src/request/nats-request-logger.interceptor.ts +89 -0
  107. package/src/request/nats-request-transport.module.ts +4 -0
  108. package/src/request/nats-request.config.ts +36 -0
  109. package/src/request/nats-request.headers.ts +33 -0
  110. package/src/request/nats-request.transport.ts +107 -0
  111. package/src/request/server.ts +260 -0
  112. package/src/request/types/consume-request-options.type.ts +5 -0
  113. package/src/request/types/formatted-nats-error.type.ts +9 -0
  114. package/src/request/types/index.ts +3 -0
  115. package/src/request/types/request-type-with-options.type.ts +8 -0
  116. package/dist/metadata.decorator.js.map +0 -1
  117. package/dist/nats-jetstream-errors.formatter.js.map +0 -1
  118. package/dist/nats-jetstream-transport.module.js.map +0 -1
  119. package/dist/nats-jetstream.config.js.map +0 -1
  120. package/dist/nats-jetstream.transport.js.map +0 -1
  121. package/dist/nats.headers.js.map +0 -1
  122. package/dist/payload.decorator.js.map +0 -1
  123. package/dist/server.js.map +0 -1
  124. package/dist/types/consume.options.js.map +0 -1
  125. package/dist/types/consumer-info.type.js.map +0 -1
  126. package/dist/types/consumers-map.type.js.map +0 -1
  127. package/dist/types/event-type-with-options.type.js.map +0 -1
  128. package/dist/types/mapping.type.js.map +0 -1
  129. package/dist/types/nats-jetstream-transport-options.type.js.map +0 -1
  130. /package/dist/{nats-jetstream-transport.module.d.ts → jetstream/nats-jetstream-transport.module.d.ts} +0 -0
  131. /package/dist/{nats-jetstream-transport.module.js → jetstream/nats-jetstream-transport.module.js} +0 -0
  132. /package/dist/{nats-jetstream.config.js → jetstream/nats-jetstream.config.js} +0 -0
  133. /package/dist/{payload.decorator.d.ts → jetstream/payload.decorator.d.ts} +0 -0
  134. /package/dist/{payload.decorator.js → jetstream/payload.decorator.js} +0 -0
  135. /package/dist/{types → jetstream/types}/consume.options.d.ts +0 -0
  136. /package/dist/{types → jetstream/types}/consume.options.js +0 -0
  137. /package/dist/{types → jetstream/types}/consumer-info.type.d.ts +0 -0
  138. /package/dist/{types → jetstream/types}/consumer-info.type.js +0 -0
  139. /package/dist/{types → jetstream/types}/consumers-map.type.d.ts +0 -0
  140. /package/dist/{types → jetstream/types}/consumers-map.type.js +0 -0
  141. /package/dist/{types → jetstream/types}/event-type-with-options.type.js +0 -0
  142. /package/dist/{types → jetstream/types}/mapping.type.d.ts +0 -0
  143. /package/dist/{types → jetstream/types}/mapping.type.js +0 -0
  144. /package/dist/{types → jetstream/types}/nats-jetstream-transport-options.type.d.ts +0 -0
  145. /package/dist/{types → jetstream/types}/nats-jetstream-transport-options.type.js +0 -0
  146. /package/src/{nats-jetstream-transport.module.ts → jetstream/nats-jetstream-transport.module.ts} +0 -0
  147. /package/src/{payload.decorator.ts → jetstream/payload.decorator.ts} +0 -0
  148. /package/src/{types → jetstream/types}/consume.options.ts +0 -0
  149. /package/src/{types → jetstream/types}/consumer-info.type.ts +0 -0
  150. /package/src/{types → jetstream/types}/consumers-map.type.ts +0 -0
  151. /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 type { ConsumerInfo } from './types/consumer-info.type';
31
- import type { ConsumersMap } from './types/consumers-map.type';
32
- import type { EventTypeWithOptions } from './types/event-type-with-options.type';
33
- import type { Mapping } from './types/mapping.type';
34
- import { EventsDeserializer } from './events.deserializer';
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('NatsJetStreamServer');
45
- override readonly deserializer = new EventsDeserializer();
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 maybeNatsContext instanceof NatsJetStreamContext;
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
- const eventType = this.getEventType(pattern);
114
- let formattedPattern: string;
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
- if (isEventType(eventType)) {
117
- this.streams.add(this.getStreamName(pattern));
118
- formattedPattern = eventType.$type;
119
- this.deserializer.events.set(formattedPattern, pattern);
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
- formattedPattern = eventType;
122
- this.streams.add(eventType);
150
+ /**
151
+ * NOTE: сейчас если указывается строка, то мы исключаем decode формата protobuf.
152
+ * Наверное стоит разделить subject и декодеры на разные поля.
153
+ */
154
+ return eventType;
123
155
  }
124
156
 
125
- super.addHandler(formattedPattern!, callback, isEventHandler, extras);
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 getEventType(pattern: any): any {
134
- return typeof pattern === 'object' && 'eventType' in pattern
135
- ? pattern.eventType
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.eventType), {
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.handleMessages(consumer, mapping, consumerName);
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 handleMessages(
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.eventType);
411
+ return contract.options?.streamName ?? getStreamName(contract.type);
450
412
  }
451
413
 
452
414
  /**
@@ -3,6 +3,6 @@ import type { EventType } from '@rsdk/events.common';
3
3
  import type { ConsumeOptions } from './consume.options';
4
4
 
5
5
  export type EventTypeWithOptions = {
6
- eventType: EventType;
6
+ type: EventType;
7
7
  options?: ConsumeOptions;
8
8
  };
@@ -0,0 +1,6 @@
1
+ export * from './consume.options';
2
+ export * from './consumer-info.type';
3
+ export * from './event-type-with-options.type';
4
+ export * from './mapping.type';
5
+ export * from './nats-jetstream-transport-options.type';
6
+ export * from './consumers-map.type';
@@ -4,5 +4,8 @@ import type { EventTypeWithOptions } from './event-type-with-options.type';
4
4
 
5
5
  export type Mapping = Map<
6
6
  string,
7
- { deserializer: EventTypeWithOptions; handler: MessageHandler }
7
+ {
8
+ deserializer: EventTypeWithOptions;
9
+ handler: MessageHandler;
10
+ }
8
11
  >;
@@ -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,4 @@
1
+ export * from './consume-request.decorator';
2
+ export * from './nats-request.transport';
3
+ export * from './types';
4
+ export * from './constants';
@@ -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,4 @@
1
+ import { Module } from '@nestjs/common';
2
+
3
+ @Module({})
4
+ export class NatsRequestTransportModule {}
@@ -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
+ }