@nestjstools/messaging 2.3.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 (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +565 -0
  3. package/lib/bus/composite-message-bus.factory.d.ts +9 -0
  4. package/lib/bus/composite-message-bus.factory.js +54 -0
  5. package/lib/bus/composite-message-bus.factory.js.map +1 -0
  6. package/lib/bus/distributed-message.bus.d.ts +11 -0
  7. package/lib/bus/distributed-message.bus.js +44 -0
  8. package/lib/bus/distributed-message.bus.js.map +1 -0
  9. package/lib/bus/i-message-bus.d.ts +4 -0
  10. package/lib/bus/i-message-bus.factory.d.ts +4 -0
  11. package/lib/bus/i-message-bus.factory.js +3 -0
  12. package/lib/bus/i-message-bus.factory.js.map +1 -0
  13. package/lib/bus/i-message-bus.js +3 -0
  14. package/lib/bus/i-message-bus.js.map +1 -0
  15. package/lib/bus/in-memory-message-bus.factory.d.ts +13 -0
  16. package/lib/bus/in-memory-message-bus.factory.js +45 -0
  17. package/lib/bus/in-memory-message-bus.factory.js.map +1 -0
  18. package/lib/bus/in-memory-message.bus.d.ts +14 -0
  19. package/lib/bus/in-memory-message.bus.js +53 -0
  20. package/lib/bus/in-memory-message.bus.js.map +1 -0
  21. package/lib/bus/message-bus.collection.d.ts +13 -0
  22. package/lib/bus/message-bus.collection.js +16 -0
  23. package/lib/bus/message-bus.collection.js.map +1 -0
  24. package/lib/channel/channel.d.ts +5 -0
  25. package/lib/channel/channel.js +10 -0
  26. package/lib/channel/channel.js.map +1 -0
  27. package/lib/channel/channel.registry.d.ts +10 -0
  28. package/lib/channel/channel.registry.js +31 -0
  29. package/lib/channel/channel.registry.js.map +1 -0
  30. package/lib/channel/factory/composite-channel.factory.d.ts +8 -0
  31. package/lib/channel/factory/composite-channel.factory.js +43 -0
  32. package/lib/channel/factory/composite-channel.factory.js.map +1 -0
  33. package/lib/channel/factory/in-memory-channel.factory.d.ts +6 -0
  34. package/lib/channel/factory/in-memory-channel.factory.js +28 -0
  35. package/lib/channel/factory/in-memory-channel.factory.js.map +1 -0
  36. package/lib/channel/i-channel-factory.d.ts +5 -0
  37. package/lib/channel/i-channel-factory.js +3 -0
  38. package/lib/channel/i-channel-factory.js.map +1 -0
  39. package/lib/channel/in-memory.channel.d.ts +4 -0
  40. package/lib/channel/in-memory.channel.js +8 -0
  41. package/lib/channel/in-memory.channel.js.map +1 -0
  42. package/lib/config.d.ts +40 -0
  43. package/lib/config.js +40 -0
  44. package/lib/config.js.map +1 -0
  45. package/lib/consumer/consumer-dispatched-message-error.d.ts +6 -0
  46. package/lib/consumer/consumer-dispatched-message-error.js +11 -0
  47. package/lib/consumer/consumer-dispatched-message-error.js.map +1 -0
  48. package/lib/consumer/consumer-message-dispatcher.d.ts +3 -0
  49. package/lib/consumer/consumer-message-dispatcher.js +3 -0
  50. package/lib/consumer/consumer-message-dispatcher.js.map +1 -0
  51. package/lib/consumer/consumer-message-mediator.d.ts +9 -0
  52. package/lib/consumer/consumer-message-mediator.js +17 -0
  53. package/lib/consumer/consumer-message-mediator.js.map +1 -0
  54. package/lib/consumer/consumer-message.d.ts +6 -0
  55. package/lib/consumer/consumer-message.js +15 -0
  56. package/lib/consumer/consumer-message.js.map +1 -0
  57. package/lib/consumer/distributed.consumer.d.ts +12 -0
  58. package/lib/consumer/distributed.consumer.js +79 -0
  59. package/lib/consumer/distributed.consumer.js.map +1 -0
  60. package/lib/consumer/i-messaging-consumer.d.ts +6 -0
  61. package/lib/consumer/i-messaging-consumer.js +3 -0
  62. package/lib/consumer/i-messaging-consumer.js.map +1 -0
  63. package/lib/dependency-injection/decorator.d.ts +13 -0
  64. package/lib/dependency-injection/decorator.js +53 -0
  65. package/lib/dependency-injection/decorator.js.map +1 -0
  66. package/lib/dependency-injection/injectable.d.ts +2 -0
  67. package/lib/dependency-injection/injectable.js +10 -0
  68. package/lib/dependency-injection/injectable.js.map +1 -0
  69. package/lib/dependency-injection/register.d.ts +4 -0
  70. package/lib/dependency-injection/register.js +59 -0
  71. package/lib/dependency-injection/register.js.map +1 -0
  72. package/lib/dependency-injection/service.d.ts +10 -0
  73. package/lib/dependency-injection/service.js +15 -0
  74. package/lib/dependency-injection/service.js.map +1 -0
  75. package/lib/exception/handler-for-message-not-found.exception.d.ts +4 -0
  76. package/lib/exception/handler-for-message-not-found.exception.js +11 -0
  77. package/lib/exception/handler-for-message-not-found.exception.js.map +1 -0
  78. package/lib/exception/invalid-channel-config.exception.d.ts +4 -0
  79. package/lib/exception/invalid-channel-config.exception.js +11 -0
  80. package/lib/exception/invalid-channel-config.exception.js.map +1 -0
  81. package/lib/exception/invalid-channel.exception.d.ts +4 -0
  82. package/lib/exception/invalid-channel.exception.js +11 -0
  83. package/lib/exception/invalid-channel.exception.js.map +1 -0
  84. package/lib/exception/messaging.exception.d.ts +3 -0
  85. package/lib/exception/messaging.exception.js +10 -0
  86. package/lib/exception/messaging.exception.js.map +1 -0
  87. package/lib/exception/unsupported-channel-factory.exception.d.ts +4 -0
  88. package/lib/exception/unsupported-channel-factory.exception.js +11 -0
  89. package/lib/exception/unsupported-channel-factory.exception.js.map +1 -0
  90. package/lib/handler/handler-response.d.ts +4 -0
  91. package/lib/handler/handler-response.js +10 -0
  92. package/lib/handler/handler-response.js.map +1 -0
  93. package/lib/handler/i-message.handler.d.ts +3 -0
  94. package/lib/handler/i-message.handler.js +3 -0
  95. package/lib/handler/i-message.handler.js.map +1 -0
  96. package/lib/handler/message-handler.registry.d.ts +6 -0
  97. package/lib/handler/message-handler.registry.js +28 -0
  98. package/lib/handler/message-handler.registry.js.map +1 -0
  99. package/lib/index.d.ts +27 -0
  100. package/lib/index.js +44 -0
  101. package/lib/index.js.map +1 -0
  102. package/lib/logger/messaging-logger.d.ts +5 -0
  103. package/lib/logger/messaging-logger.js +3 -0
  104. package/lib/logger/messaging-logger.js.map +1 -0
  105. package/lib/logger/nest-logger.d.ts +12 -0
  106. package/lib/logger/nest-logger.js +43 -0
  107. package/lib/logger/nest-logger.js.map +1 -0
  108. package/lib/message/default-message-options.d.ts +8 -0
  109. package/lib/message/default-message-options.js +12 -0
  110. package/lib/message/default-message-options.js.map +1 -0
  111. package/lib/message/message-options.d.ts +5 -0
  112. package/lib/message/message-options.js +3 -0
  113. package/lib/message/message-options.js.map +1 -0
  114. package/lib/message/message-response.d.ts +6 -0
  115. package/lib/message/message-response.js +19 -0
  116. package/lib/message/message-response.js.map +1 -0
  117. package/lib/message/message.d.ts +6 -0
  118. package/lib/message/message.factory.d.ts +7 -0
  119. package/lib/message/message.factory.js +15 -0
  120. package/lib/message/message.factory.js.map +1 -0
  121. package/lib/message/message.js +3 -0
  122. package/lib/message/message.js.map +1 -0
  123. package/lib/message/routing-message.d.ts +9 -0
  124. package/lib/message/routing-message.js +15 -0
  125. package/lib/message/routing-message.js.map +1 -0
  126. package/lib/message/sealed-routing-message.d.ts +9 -0
  127. package/lib/message/sealed-routing-message.js +15 -0
  128. package/lib/message/sealed-routing-message.js.map +1 -0
  129. package/lib/messaging.module.d.ts +10 -0
  130. package/lib/messaging.module.js +150 -0
  131. package/lib/messaging.module.js.map +1 -0
  132. package/lib/middleware/handler-middleware.d.ts +9 -0
  133. package/lib/middleware/handler-middleware.js +40 -0
  134. package/lib/middleware/handler-middleware.js.map +1 -0
  135. package/lib/middleware/middleware.context.d.ts +8 -0
  136. package/lib/middleware/middleware.context.js +18 -0
  137. package/lib/middleware/middleware.context.js.map +1 -0
  138. package/lib/middleware/middleware.d.ts +5 -0
  139. package/lib/middleware/middleware.js +3 -0
  140. package/lib/middleware/middleware.js.map +1 -0
  141. package/lib/middleware/middleware.registry.d.ts +7 -0
  142. package/lib/middleware/middleware.registry.js +26 -0
  143. package/lib/middleware/middleware.registry.js.map +1 -0
  144. package/lib/normalizer/message-normalizer.d.ts +4 -0
  145. package/lib/normalizer/message-normalizer.js +3 -0
  146. package/lib/normalizer/message-normalizer.js.map +1 -0
  147. package/lib/normalizer/normalizer.registry.d.ts +7 -0
  148. package/lib/normalizer/normalizer.registry.js +26 -0
  149. package/lib/normalizer/normalizer.registry.js.map +1 -0
  150. package/lib/normalizer/object-forward-message.normalizer.d.ts +5 -0
  151. package/lib/normalizer/object-forward-message.normalizer.js +26 -0
  152. package/lib/normalizer/object-forward-message.normalizer.js.map +1 -0
  153. package/lib/shared/decorator-extractor.d.ts +3 -0
  154. package/lib/shared/decorator-extractor.js +11 -0
  155. package/lib/shared/decorator-extractor.js.map +1 -0
  156. package/lib/tsconfig.build.tsbuildinfo +1 -0
  157. package/package.json +100 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 nestjstools
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,565 @@
1
+ <p align="center">
2
+ <image src="nestjstools-logo.png" width="400">
3
+ </p>
4
+
5
+ # @nestjstools/messaging
6
+
7
+ A NestJS library for managing asynchronous and synchronous messages (service bus) with support for buses, handlers, channels, and consumers. This library simplifies building scalable and decoupled applications by facilitating robust message handling pipelines while ensuring flexibility and reliability.
8
+
9
+ ---
10
+
11
+ ## Features
12
+ - **Message Buses**: Define multiple buses for commands, events, and queries to streamline message routing.
13
+ - **Handlers**: Easily register and manage handlers for processing messages.
14
+ - **Channels**: Support for in-memory channels and **easy extension** to create custom channel implementations tailored to your needs.
15
+ - **Consumers**: Run message consumers to process queued messages asynchronously, ensuring system reliability and fault tolerance.
16
+ - **Middleware Support**: Add custom middleware for message transformation such like validation, logging - do whatever you want.
17
+ - **Debug Mode**: Enable enhanced logging and debugging capabilities for development.
18
+ - **Extensibility**: Creating new channels is straightforward, allowing developers to expand and integrate with external systems or protocols effortlessly.
19
+
20
+ ---
21
+
22
+ ## Example project based on RaabitMQ example
23
+
24
+ Repository: https://github.com/nestjstools/messaging-rabbitmq-example
25
+
26
+ ---
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install @nestjstools/messaging
32
+ ```
33
+
34
+ or
35
+
36
+ ```bash
37
+ yarn add @nestjstools/messaging
38
+ ```
39
+
40
+ ## Getting Started
41
+
42
+ ### Basic Usage (In-memory)
43
+
44
+ ```typescript
45
+ import { MessagingModule } from '@nestjstools/messaging';
46
+ import { InMemoryChannelConfig } from '@nestjstools/messaging/channels';
47
+ import { SendMessageHandler } from './handlers/send-message.handler';
48
+
49
+ @Module({
50
+ imports: [
51
+ MessagingModule.forRoot({
52
+ buses: [
53
+ {
54
+ name: 'message.bus',
55
+ channels: ['my-channel'],
56
+ },
57
+ ],
58
+ channels: [
59
+ new InMemoryChannelConfig({
60
+ name: 'my-channel',
61
+ middlewares: [],
62
+ }),
63
+ ],
64
+ debug: true,
65
+ }),
66
+ ],
67
+ })
68
+ export class AppModule {}
69
+ ```
70
+
71
+ ### Define a Message & Message Handler
72
+
73
+ Create a new handler that processes specific message
74
+
75
+ #### Define your message
76
+ ```typescript
77
+ export class SendMessage {
78
+ constructor(
79
+ public readonly content: string,
80
+ ) {
81
+ }
82
+ }
83
+
84
+ ```
85
+ #### Define your message handler
86
+ ```typescript
87
+ import { SendMessage } from './send-message';
88
+ import { MessageResponse } from '@nestjstools/messaging/message';
89
+ import { MessageHandler } from '@nestjstools/messaging/decorators';
90
+ import { IMessageHandler } from '@nestjstools/messaging/interfaces';
91
+ import { Injectable } from '@nestjs/common';
92
+
93
+ @Injectable()
94
+ @MessageHandler('your.message')
95
+ export class SendMessageHandler implements IMessageHandler<SendMessage> {
96
+ async handle(message: SendMessage): Promise<MessageResponse | void> {
97
+ console.log(message.content);
98
+ // Example handling logic
99
+ }
100
+ }
101
+ ```
102
+
103
+ ### Next Step: Dispatching a Message
104
+
105
+ Messages can be dispatched from anywhere in your application—whether from services, controllers, or other components. Here’s an example using an HTTP endpoint:
106
+
107
+ ```typescript
108
+ import { Controller, Get } from '@nestjs/common';
109
+ import { MessageBus } from '@nestjstools/messaging/dependency-injection';
110
+ import { IMessageBus } from '@nestjstools/messaging/bus';
111
+ import { RoutingMessage } from '@nestjstools/messaging/message';
112
+ import { SendMessage } from './test/send-message';
113
+
114
+ @Controller()
115
+ export class AppController {
116
+ //You can inject every bus which you defined in configuration
117
+ constructor(@MessageBus('message.bus') private readonly messageBus: IMessageBus) {}
118
+
119
+ @Get()
120
+ async dispatchMessage(): Promise<string> {
121
+ // Dispatching a SendMessage instance with a route
122
+ await this.messageBus.dispatch(
123
+ new RoutingMessage(new SendMessage('Message from HTTP request'), 'your.message'),
124
+ );
125
+
126
+ return 'Message dispatched successfully!';
127
+ }
128
+ }
129
+ ```
130
+
131
+ ### Explanation:
132
+
133
+ 1. **Flexible Dispatching**:
134
+ - You can call the `dispatch` method from any layer (e.g., controller, service, or scheduled job). This example uses an HTTP `GET` endpoint for demonstration.
135
+
136
+ 2. **`@MessageBus` Decorator**:
137
+ - Injects the particular message bus (identified by its name, `message.bus`) into the `AppController`.
138
+
139
+ 3. **Routing and Payload**:
140
+ - Wrap the payload (`SendMessage`) in a `RoutingMessage` to specify its route (`your.message`), which ensures the message is handled by the appropriate handler.
141
+
142
+ 4. **HTTP Trigger**:
143
+ - This implementation illustrates an entry point triggered via an HTTP request, showcasing how simple it is to connect the messaging system to a web interface.
144
+
145
+ ### ⚠️ Warning!
146
+ 🚨 Important Notice: You can return responses from handlers, but currently, it only works with the `InMemoryChannel`. This behavior may not function as expected if multiple handlers are processing a single message.
147
+
148
+ 🛠️ Please ensure you're using a compatible setup when working with multiple handlers, as this could result in unexpected behavior.
149
+
150
+ ---
151
+
152
+ ## RabbitMQ Integration: Messaging Configuration Example
153
+
154
+ ---
155
+
156
+ The `MessagingModule` provides out-of-the-box integration with **RabbitMQ**, enabling the use of **AMQP** channels alongside in-memory channels. The configuration below demonstrates **CQRS** by separating command and event buses, ensuring seamless integration of your application with RabbitMQ's flexible messaging capabilities for both **command** and **event handling** + command dispatching.
157
+
158
+ ## To make it works for rabbitmq
159
+ We need to install rabbitmq extension for `@nestjstools/messaging`
160
+
161
+ ```bash
162
+ npm install @nestjstools/messaging-rabbitmq-extension
163
+ ```
164
+
165
+ or
166
+
167
+ ```bash
168
+ yarn add @nestjstools/messaging-rabbitmq-extension
169
+ ```
170
+
171
+ ```typescript
172
+ import { MessagingModule } from '@nestjstools/messaging';
173
+ import { InMemoryChannelConfig, AmqpChannelConfig, ExchangeType } from '@nestjstools/messaging/channels';
174
+ import { SendMessageHandler } from './handlers/send-message.handler';
175
+
176
+ @Module({
177
+ imports: [
178
+ MessagingModule.forRoot({
179
+ buses: [
180
+ {
181
+ name: 'message.bus',
182
+ channels: ['my-channel'],
183
+ },
184
+ {
185
+ name: 'command-bus', // The naming is very flexible
186
+ channels: ['amqp-command'], // Be sure if you defined same channels name as you defined below
187
+ },
188
+ {
189
+ name: 'event-bus',
190
+ channels: ['amqp-event'],
191
+ },
192
+ ],
193
+ channels: [
194
+ new InMemoryChannelConfig({
195
+ name: 'my-channel',
196
+ middlewares: [],
197
+ }),
198
+ new AmqpChannelConfig({
199
+ name: 'amqp-command',
200
+ connectionUri: 'amqp://guest:guest@localhost:5672/',
201
+ exchangeName: 'my_app_command.exchange',
202
+ bindingKeys: ['my_app.command.#'],
203
+ exchangeType: ExchangeType.TOPIC,
204
+ middlewares: [],
205
+ queue: 'my_app.command',
206
+ autoCreate: true, // Create exchange, queue & bind keys
207
+ }),
208
+ new AmqpChannelConfig({
209
+ name: 'amqp-event',
210
+ connectionUri: 'amqp://guest:guest@localhost:5672/',
211
+ exchangeName: 'my_app_event.exchange',
212
+ bindingKeys: ['my_app_event.#'],
213
+ exchangeType: ExchangeType.TOPIC,
214
+ queue: 'my_app.event',
215
+ avoidErrorsForNotExistedHandlers: true, // We can avoid errors if we don't have handler yet for the event
216
+ autoCreate: true,
217
+ }),
218
+ ],
219
+ debug: true,
220
+ }),
221
+ ],
222
+ })
223
+ export class AppModule {}
224
+ ```
225
+
226
+ ---
227
+
228
+ ### Key Features:
229
+
230
+ 1. **Multiple Message Buses**:
231
+ - Configure distinct buses for **in-memory**, **commands**, and **events**:
232
+ - `message.bus` (in-memory).
233
+ - `command.message-bus` (AMQP command processing).
234
+ - `event.message-bus` (AMQP event processing).
235
+
236
+ 2. **In-Memory Channel**:
237
+ - Simple and lightweight channel suitable for non-persistent messaging or testing purposes.
238
+
239
+ 3. **AMQP Channels**:
240
+ - Fully integrated RabbitMQ channel configuration using `AmqpChannelConfig`.
241
+
242
+ 4. **Channel Details**:
243
+ - `connectionUri`: Specifies the RabbitMQ server connection.
244
+ - `exchangeName`: The AMQP exchange to publish or consume messages from.
245
+ - `bindingKeys`: Define message routing patterns using wildcards (e.g., `my_app.command.#`).
246
+ - `exchangeType`: Supports RabbitMQ exchange types such as `TOPIC`.
247
+ - `queue`: Specify a RabbitMQ queue to consume messages from.
248
+ - `autoCreate`: Automatically creates the exchange, queue, and bindings if they don’t exist.
249
+
250
+ 5. **Error Handling**:
251
+ - Use `avoidErrorsForNotExistedHandlers` in `amqp-event` to gracefully handle missing handlers for event messages.
252
+
253
+ 6. **Debug Mode**:
254
+ - Enable `debug: true` to assist in monitoring and troubleshooting messages.
255
+
256
+ This configuration provides a solid foundation for integrating RabbitMQ as part of your messaging system. It facilitates the decoupling of commands, events, and in-memory operations, ensuring reliable and scalable communication across distributed systems.
257
+
258
+ ---
259
+
260
+ ## Mapping Messages in RabbitMQ Channel
261
+
262
+ ### Topic Exchange
263
+ For optimal routing, it's recommended to use routing keys as part of the binding key. For example, if you bind a queue with the key `my_app.command.#`, messages with routing keys like `my_app.command.domain.action` will automatically be routed to that queue. This ensures that any message with a routing key starting with `my_app.command` is directed to the appropriate queue.
264
+ Here's a more concise and clear version of your explanation:
265
+
266
+ ### Direct Exchange
267
+ Ensure your queue has defined binding keys, as messages will be routed to queues based on these keys. If no binding keys are defined, the routing key in RabbitMQ will default to the routing key specified in the handler.
268
+
269
+ ### Additional
270
+ * You can override message routing using `AmqpMessageOptions`. This allows sending a message to a specified exchange and routing it with a custom key.
271
+ ```typescript
272
+ this.messageBus.dispatch(new RoutingMessage(new SendMessage('Hello Rabbit!'), 'app.command.execute', new AmqpMessageOptions('exchange_name', 'rabbitmq_routing_key_to_queue')));
273
+ ```
274
+
275
+ ---
276
+ ## Normalizers
277
+ What is a Normalizer?
278
+ A Normalizer is a component that transforms messages between different formats. It ensures that messages are correctly encoded when sent and properly decoded when received. This is particularly useful in messaging systems where messages need to be serialized and deserialized efficiently.
279
+
280
+ You can use it to make it works with:
281
+ * [protobuf](https://protobuf.dev/)
282
+ * Custom JSONs
283
+ * Base64
284
+ * Any custom format
285
+
286
+ ```typescript
287
+ import { Injectable } from '@nestjs/common';
288
+ import { MessagingNormalizer, MessageNormalizer } from '@nestjstools/messaging';
289
+ import { Buffer } from 'buffer';
290
+
291
+ @Injectable()
292
+ @MessagingNormalizer()
293
+ export class Base64Normalizer implements MessageNormalizer {
294
+ denormalize(message: string | object, type: string): Promise<object> {
295
+ if (typeof message === 'object') {
296
+ throw new Error('Message must be a string!');
297
+ }
298
+ return Promise.resolve(JSON.parse(Buffer.from(message, 'base64').toString('utf-8')));
299
+ }
300
+
301
+ normalize(message: object, type: string): Promise<string> {
302
+ const jsonString = JSON.stringify(message);
303
+ return Promise.resolve(Buffer.from(jsonString, 'utf-8').toString('base64'));
304
+ }
305
+ }
306
+
307
+ ```
308
+ ### How It Works
309
+ #### Normalization (normalize)
310
+ * Converts a JSON object to a Base64 string before sending.
311
+ #### Denormalization (denormalize)
312
+ * Decodes the Base64 string back into a JSON object after receiving.
313
+
314
+ You can define a **Normalizer** per Channel
315
+ ___
316
+
317
+ ## <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M160-40v-240h100v-80H160v-240h100v-80H160v-240h280v240H340v80h100v80h120v-80h280v240H560v-80H440v80H340v80h100v240H160Zm80-80h120v-80H240v80Zm0-320h120v-80H240v80Zm400 0h120v-80H640v80ZM240-760h120v-80H240v80Zm60-40Zm0 320Zm400 0ZM300-160Z"/></svg> Middlewares
318
+
319
+ A **middleware** in the context of the `MessagingModule` is a function that processes messages as they pass through the message pipeline. The middleware can intercept, modify, or log messages before they are handled by the respective **message handler**. This is particularly useful for logging, authentication, validation, or any other pre-processing step before the actual business logic is applied.
320
+
321
+ Each **channel** in the messaging system has its own set of middlewares, and these middlewares are executed in order when a message is dispatched through the respective channel.
322
+
323
+ ### How to Use Middleware in Messaging Channels:
324
+
325
+ To use middleware, you need to:
326
+
327
+ 1. **Define the middleware class** that implements the `Middleware` interface, which contains the `process` method that processes the message.
328
+ 2. **Attach the middleware to a specific channel** via the channel configuration.
329
+
330
+ ### Example Middleware Code:
331
+
332
+ Here's an example middleware class that logs a message when the middleware is applied.
333
+
334
+ ```typescript
335
+ import { Injectable } from '@nestjs/common';
336
+ import { Middleware } from '@nestjstools/messaging/middleware/middleware';
337
+ import { RoutingMessage } from '@nestjstools/messaging/message/routing-message';
338
+
339
+ @Injectable()
340
+ @MessagingMiddleware('TestMiddleware-random-name')
341
+ export class TestMiddleware implements Middleware {
342
+ async process(message: RoutingMessage, context: MiddlewareContext): Promise<MiddlewareContext> {
343
+ console.log('!!!! WORKS'); // Log or process the message here
344
+
345
+ return await context.next().process(message, context); //TODO call `next()` method from `MiddlewareContext` to process next middleware
346
+ }
347
+ }
348
+ ```
349
+
350
+ ### Attaching Middleware to a Channel:
351
+
352
+ Now that we've defined the middleware, it needs to be attached to a specific channel in the `MessagingModule` configuration. Here's how you would configure the middleware for a channel:
353
+
354
+ ```typescript
355
+ import { MessagingModule } from '@nestjstools/messaging';
356
+ import { AmqpChannelConfig, InMemoryChannelConfig } from '@nestjstools/messaging/channels';
357
+ import { TestMiddleware } from './middlewares/test.middleware';
358
+ import { SendMessageHandler } from './handlers/send-message.handler';
359
+
360
+ @Module({
361
+ imports: [
362
+ MessagingModule.forRoot({
363
+ buses: [
364
+ {
365
+ name: 'message.bus',
366
+ channels: ['my-channel'],
367
+ },
368
+ ],
369
+ channels: [
370
+ new InMemoryChannelConfig({
371
+ name: 'my-channel',
372
+ middlewares: [TestMiddleware], // Attach TestMiddleware to this channel
373
+ }),
374
+ new AmqpChannelConfig({
375
+ name: 'amqp-command',
376
+ connectionUri: 'amqp://guest:guest@localhost:5672/',
377
+ exchangeName: 'my_app_command.exchange',
378
+ bindingKeys: ['my_app.command.#'],
379
+ exchangeType: ExchangeType.TOPIC,
380
+ queue: 'my_app.command',
381
+ autoCreate: true,
382
+ enableConsumer: true,
383
+ middlewares: [TestMiddleware], // Attach TestMiddleware to this AMQP channel
384
+ }),
385
+ ],
386
+ debug: true,
387
+ }),
388
+ ],
389
+ })
390
+ export class AppModule {}
391
+ ```
392
+
393
+ ### Explanation of How It Works:
394
+
395
+ 1. **Middleware Class**:
396
+ - A `Middleware` is a class that implements the `next` method. In this case, the `TestMiddleware` simply logs `'!!!! WORKS'` and allows the message to continue.
397
+
398
+ 2. **Message Pipeline**:
399
+ - When a message is dispatched, it passes through the series of middlewares configured for its channel.
400
+ - The middlewares execute in the order they're listed for the channel, and each `next` method decides what happens to the message—whether it continues or gets transformed.
401
+
402
+ 3. **Channel-Specific Middlewares**:
403
+ - Each channel can have its own set of middlewares defined in the channel's configuration (e.g., `InMemoryChannelConfig` and `AmqpChannelConfig`).
404
+ - This allows flexible control of how messages are processed depending on the channel, enabling different logic for each transport mechanism (in-memory vs. RabbitMQ).
405
+
406
+ ### Benefits of Using Middlewares:
407
+ - **Separation of Concerns**: Middlewares help encapsulate cross-cutting concerns like logging, validation, and authentication, making the code cleaner.
408
+ - **Reusability**: A middleware can be reused across different channels to perform the same actions on various messages.
409
+ - **Custom Logic**: You can apply custom transformations, logging, or other types of business logic to messages as they move through the pipeline.
410
+
411
+ ---
412
+
413
+ ## Configuration options
414
+ Here’s a table with the documentation for the `MessagingModule.forRoot` configuration you requested, breaking it into **buses**, **channels** (with descriptions of both channels), and their respective properties, descriptions, and default values:
415
+
416
+ ### `MessagingModule.forRoot` Configuration
417
+ <br>
418
+
419
+ | **Property** | **Description** | **Default Value** |
420
+ |----------------|------------------------------------------------------------------------|-------------------------------|
421
+ | **`buses`** | Array of message buses that define routing and processing of messages. | `[]` (empty array by default) |
422
+ | **`channels`** | Array of channel configurations used by the message buses. | `[]` (empty array by default) |
423
+ | **`debug`** | Enables or disables debug mode for logging additional messages. | `false` |
424
+ | **`logging`** | Enables or disables logging for bus activity (e.g., message dispatch). | `false` |
425
+
426
+ ---
427
+
428
+ ### Buses
429
+
430
+ | **Property** | **Description** | **Default Value** |
431
+ |----------------|----------------------------------------------------------------------|-------------------|
432
+ | **`name`** | Name of the message bus (e.g., 'command.message-bus'). | |
433
+ | **`channels`** | List of channel names to be used by this bus (e.g., `'my-channel'`). | `[]` |
434
+
435
+ ---
436
+
437
+ ### Channels
438
+
439
+ #### 1. **InMemoryChannelConfig**
440
+
441
+ | **Property** | **Description** | **Default Value** |
442
+ |----------------------------------------|----------------------------------------------------------|-------------------|
443
+ | **`name`** | Name of the in-memory channel (e.g., `'my-channel'`). | |
444
+ | **`middlewares`** | List of middlewares to apply to the channel. | `[]` |
445
+ | **`avoidErrorsForNotExistedHandlers`** | Avoid errors if no handler is available for the message. | `false` |
446
+ | **`normalizer`** | Set your custom normalizer for messages | |
447
+
448
+ #### 2. **AmqpChannelConfig**
449
+
450
+ | **Property** | **Description** | **Default Value** |
451
+ |----------------------------------------|----------------------------------------------------------------------------------|-------------------|
452
+ | **`name`** | Name of the AMQP channel (e.g., `'amqp-command'`). | |
453
+ | **`connectionUri`** | URI for the RabbitMQ connection, such as `'amqp://guest:guest@localhost:5672/'`. | |
454
+ | **`exchangeName`** | The AMQP exchange name (e.g., `'my_app.exchange'`). | |
455
+ | **`bindingKeys`** | The routing keys to bind to (e.g., `['my_app.command.#']`). | `[]` |
456
+ | **`exchangeType`** | Type of the RabbitMQ exchange (e.g., `TOPIC`). | |
457
+ | **`queue`** | The AMQP queue to consume messages from (e.g., `'my_app.command'`). | |
458
+ | **`autoCreate`** | Automatically creates the exchange, queue, and bindings if they don’t exist. | `true` |
459
+ | **`enableConsumer`** | Enables or disables the consumer for this channel. | `true` |
460
+ | **`avoidErrorsForNotExistedHandlers`** | Avoid errors if no handler is available for the message. | `false` |
461
+ | **`normalizer`** | Set your custom normalizer for messages | |
462
+
463
+ This table provides a structured overview of the **`MessagingModule.forRoot`** configuration, with details about each property within **buses** and **channels** and their corresponding default values.
464
+
465
+ ## Creating Your Own Channel and Bus
466
+ This process allows you to define and integrate a custom **Channel** and **MessageBus** for your application, giving you complete flexibility and control over how messages are processed, dispatched, and consumed. Each step provides the necessary building blocks to create your own transport layer with full integration into the `MessagingModule`.
467
+
468
+ ### 1. Create a `ChannelConfig`
469
+ A `ChannelConfig` class holds the configuration required to establish a stable connection to your service (e.g., RabbitMQ, Redis, etc.). Your class should implement the `ChannelConfig` interface and define necessary data like the channel name and middlewares.
470
+
471
+ ```typescript
472
+ export class YourChannelConfig implements ChannelConfig {
473
+ public readonly name: string;
474
+ public readonly middlewares: object[];
475
+
476
+ constructor({ name, middlewares }: AmqpChannelConfig) {
477
+ this.name = name;
478
+ this.middlewares = middlewares ?? []; // Default to empty array if no middlewares provided
479
+ }
480
+ }
481
+ ```
482
+
483
+ ### 2. Create a `Channel`
484
+ Next, create a class that implements the `Channel` interface. This class will serve as your `DataSource` and utilize the configuration you defined in the `ChannelConfig` class.
485
+
486
+ ```typescript
487
+ export class YourChannel extends Channel {}
488
+ ```
489
+
490
+ ### 3. Create a `ChannelFactory`
491
+ A `ChannelFactory` is responsible for creating instances of your custom `Channel` class. It implements the `IChannelFactory` interface and ensures proper injection into your app.
492
+
493
+ ```typescript
494
+ @Injectable()
495
+ @ChannelFactory(YourChannel)
496
+ export class YourChannelFactory implements IChannelFactory<YourChannelConfig> {
497
+ create(channelConfig: YourChannelConfig): Channel {
498
+ return new YourChannel(channelConfig);
499
+ }
500
+ }
501
+ ```
502
+
503
+ ### 4. Create a `MessageBus`
504
+ The `MessageBus` handles the dispatching of messages in your system. Create a class implementing the `IMessageBus` interface to send messages to your custom service (e.g., RabbitMQ, Redis, etc.).
505
+
506
+ ```typescript
507
+ export class YourMessageBus implements IMessageBus {
508
+ constructor(private readonly yourChannel: YourChannel) {}
509
+
510
+ async dispatch(message: RoutingMessage): Promise<MessageResponse | void> {
511
+ // Write your logic here to dispatch the message to your channel (e.g., RabbitMQ)
512
+ }
513
+ }
514
+ ```
515
+
516
+ ### 5. Create a `MessageBusFactory`
517
+ The `MessageBusFactory` creates instances of your `MessageBus` and ensures it's properly integrated with your `Channel`. It implements the `IMessageBusFactory` interface.
518
+
519
+ ```typescript
520
+ @Injectable()
521
+ @MessageBusFactory(YourChannel)
522
+ export class YourMessageBusFactory implements IMessageBusFactory<YourChannel> {
523
+ create(channel: YourChannel): IMessageBus {
524
+ return new YourMessageBus(channel); // Return a new instance of your message bus
525
+ }
526
+ }
527
+ ```
528
+
529
+ ### 6. Create a Consumer `MessageConsumer`
530
+ A consumer receives and processes messages. Create a class that implements the `IMessagingConsumer` interface and handle the message processing within the `consume` method.
531
+
532
+ ```typescript
533
+ @Injectable()
534
+ @MessageConsumer(YourChannel)
535
+ export class YourMessagingConsumer implements IMessagingConsumer<YourChannel> {
536
+ async consume(dispatcher: ConsumerMessageDispatcher, channel: YourChannel): Promise<void> {
537
+ // Logic to consume a message...
538
+ //TODO dispatcher.dispatch(new ConsumerMessage(...));
539
+
540
+ return Promise.resolve();
541
+ }
542
+
543
+ onError(errored: ConsumerDispatchedMessageError, channel: YourChannel): Promise<void> {
544
+ // Handle error if message processing fails
545
+ return Promise.resolve();
546
+ }
547
+ }
548
+ ```
549
+
550
+ ### 7. Add Custom `MessageOptions` to Your Bus (Optional)
551
+ You can create custom message options for your message.
552
+
553
+ ```typescript
554
+ export class YourMessageOptions implements MessageOptions {
555
+ constructor(public readonly middlewares: Middleware[] = []) {}
556
+ }
557
+ ```
558
+
559
+ Classes with `Injectable()` decorator must be defined as providers in somewhere in application.
560
+
561
+ ---
562
+
563
+ ### Future features
564
+ * INBOX & OUTBOX Pattern
565
+ * Supports other adapters such like Redis
@@ -0,0 +1,9 @@
1
+ import { DiscoveryService } from '@nestjs/core';
2
+ import { IMessageBus } from './i-message-bus';
3
+ import { Channel } from '../channel/channel';
4
+ export declare class CompositeMessageBusFactory {
5
+ private readonly defaultMessageBus;
6
+ private readonly discoveryService;
7
+ constructor(defaultMessageBus: IMessageBus, discoveryService: DiscoveryService);
8
+ create(channel: Channel<any>): IMessageBus;
9
+ }
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.CompositeMessageBusFactory = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const core_1 = require("@nestjs/core");
18
+ const injectable_1 = require("../dependency-injection/injectable");
19
+ const in_memory_channel_1 = require("../channel/in-memory.channel");
20
+ const decorator_1 = require("../dependency-injection/decorator");
21
+ const messaging_exception_1 = require("../exception/messaging.exception");
22
+ let CompositeMessageBusFactory = class CompositeMessageBusFactory {
23
+ constructor(defaultMessageBus, discoveryService) {
24
+ this.defaultMessageBus = defaultMessageBus;
25
+ this.discoveryService = discoveryService;
26
+ }
27
+ create(channel) {
28
+ if (channel instanceof in_memory_channel_1.InMemoryChannel &&
29
+ 'default.bus' === channel.config.name) {
30
+ return this.defaultMessageBus;
31
+ }
32
+ const factory = this.discoveryService
33
+ .getProviders()
34
+ .filter((provider) => {
35
+ if (!provider.metatype) {
36
+ return false;
37
+ }
38
+ return Reflect.hasMetadata(decorator_1.MESSAGE_BUS_FACTORY_METADATA, provider.metatype);
39
+ })
40
+ .filter((factory) => Reflect.getMetadata(decorator_1.MESSAGE_BUS_FACTORY_METADATA, factory.metatype)
41
+ .name === channel.constructor.name);
42
+ if (factory.length !== 1) {
43
+ throw new messaging_exception_1.MessagingException(`Unsupported message bus factory for channel ${channel.constructor.name}`);
44
+ }
45
+ return factory[0].instance.create(channel);
46
+ }
47
+ };
48
+ exports.CompositeMessageBusFactory = CompositeMessageBusFactory;
49
+ exports.CompositeMessageBusFactory = CompositeMessageBusFactory = __decorate([
50
+ (0, common_1.Injectable)(),
51
+ __param(0, (0, injectable_1.DefaultMessageBus)()),
52
+ __metadata("design:paramtypes", [Object, core_1.DiscoveryService])
53
+ ], CompositeMessageBusFactory);
54
+ //# sourceMappingURL=composite-message-bus.factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composite-message-bus.factory.js","sourceRoot":"","sources":["../../src/bus/composite-message-bus.factory.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAA4C;AAC5C,uCAAgD;AAChD,mEAAuE;AAGvE,oEAA+D;AAC/D,iEAAiF;AACjF,0EAAsE;AAG/D,IAAM,0BAA0B,GAAhC,MAAM,0BAA0B;IACrC,YACwC,iBAA8B,EACnD,gBAAkC;QADb,sBAAiB,GAAjB,iBAAiB,CAAa;QACnD,qBAAgB,GAAhB,gBAAgB,CAAkB;IAClD,CAAC;IAEJ,MAAM,CAAC,OAAqB;QAC1B,IACE,OAAO,YAAY,mCAAe;YAClC,aAAa,KAAK,OAAO,CAAC,MAAM,CAAC,IAAI,EACrC,CAAC;YACD,OAAO,IAAI,CAAC,iBAAiB,CAAC;QAChC,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,gBAAgB;aAClC,YAAY,EAAE;aACd,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE;YACnB,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;gBACvB,OAAO,KAAK,CAAC;YACf,CAAC;YAED,OAAO,OAAO,CAAC,WAAW,CACxB,wCAA4B,EAC5B,QAAQ,CAAC,QAAQ,CAClB,CAAC;QACJ,CAAC,CAAC;aACD,MAAM,CACL,CAAC,OAAO,EAAE,EAAE,CACV,OAAO,CAAC,WAAW,CAAC,wCAA4B,EAAE,OAAO,CAAC,QAAQ,CAAC;aAChE,IAAI,KAAK,OAAO,CAAC,WAAW,CAAC,IAAI,CACvC,CAAC;QAEJ,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,wCAAkB,CAC1B,+CAA+C,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,CAC1E,CAAC;QACJ,CAAC;QAED,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7C,CAAC;CACF,CAAA;AAxCY,gEAA0B;qCAA1B,0BAA0B;IADtC,IAAA,mBAAU,GAAE;IAGR,WAAA,IAAA,8BAAiB,GAAE,CAAA;6CACe,uBAAgB;GAH1C,0BAA0B,CAwCtC"}
@@ -0,0 +1,11 @@
1
+ import { MessageResponse } from '../message/message-response';
2
+ import { IMessageBus } from './i-message-bus';
3
+ import { MessageBusCollection } from './message-bus.collection';
4
+ import { RoutingMessage } from '../message/routing-message';
5
+ import { NormalizerRegistry } from '../normalizer/normalizer.registry';
6
+ export declare class DistributedMessageBus implements IMessageBus {
7
+ private messageBusCollection;
8
+ private normalizerRegistry;
9
+ constructor(messageBusCollection: MessageBusCollection, normalizerRegistry: NormalizerRegistry);
10
+ dispatch(message: RoutingMessage): Promise<MessageResponse | void>;
11
+ }