@grupodiariodaregiao/bunstone 0.3.12 → 0.4.0
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/index.js +59 -1
- package/dist/lib/rabbitmq/interfaces/rabbitmq-message.interface.d.ts +39 -1
- package/dist/lib/rabbitmq/rabbitmq-connection.d.ts +14 -0
- package/lib/app-startup.ts +70 -1
- package/lib/rabbitmq/interfaces/rabbitmq-message.interface.ts +39 -1
- package/lib/rabbitmq/rabbitmq-connection.ts +36 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -116733,6 +116733,20 @@ class RabbitMQConnection {
|
|
|
116733
116733
|
RabbitMQConnection.consumerChannels.set(queue2, channel);
|
|
116734
116734
|
return channel;
|
|
116735
116735
|
}
|
|
116736
|
+
static async createRoutingKeyConsumerChannel(exchange, routingKey) {
|
|
116737
|
+
const connection2 = await RabbitMQConnection.getConnection();
|
|
116738
|
+
const channel = await connection2.createChannel();
|
|
116739
|
+
const prefetch = RabbitMQConnection.options?.prefetch ?? 10;
|
|
116740
|
+
await channel.prefetch(prefetch);
|
|
116741
|
+
const { queue: queueName } = await channel.assertQueue("", {
|
|
116742
|
+
exclusive: true,
|
|
116743
|
+
autoDelete: true,
|
|
116744
|
+
durable: false
|
|
116745
|
+
});
|
|
116746
|
+
await channel.bindQueue(queueName, exchange, routingKey);
|
|
116747
|
+
logger2.log(`Routing-key consumer queue "${queueName}" bound to exchange "${exchange}" with key "${routingKey}"`);
|
|
116748
|
+
return { channel, queueName };
|
|
116749
|
+
}
|
|
116736
116750
|
static async initialise() {
|
|
116737
116751
|
await RabbitMQConnection.getConnection();
|
|
116738
116752
|
}
|
|
@@ -117579,7 +117593,51 @@ if (document.readyState === 'loading') {
|
|
|
117579
117593
|
for (const [providerClass, descriptors] of providersRabbitMQ.entries()) {
|
|
117580
117594
|
const instance = injectables?.get(providerClass) ?? new providerClass;
|
|
117581
117595
|
for (const descriptor of descriptors) {
|
|
117582
|
-
const {
|
|
117596
|
+
const {
|
|
117597
|
+
queue: queue2,
|
|
117598
|
+
exchange,
|
|
117599
|
+
routingKey,
|
|
117600
|
+
noAck = false
|
|
117601
|
+
} = descriptor.options;
|
|
117602
|
+
if (exchange && routingKey) {
|
|
117603
|
+
AppStartup.logger.log(`Registering RabbitMQ consumer for exchange: "${exchange}" routingKey: "${routingKey}" \u2192 ${providerClass.name}.${descriptor.methodName}()`);
|
|
117604
|
+
try {
|
|
117605
|
+
const { channel, queueName } = await RabbitMQConnection.createRoutingKeyConsumerChannel(exchange, routingKey);
|
|
117606
|
+
await channel.consume(queueName, async (raw) => {
|
|
117607
|
+
if (!raw)
|
|
117608
|
+
return;
|
|
117609
|
+
const data = (() => {
|
|
117610
|
+
try {
|
|
117611
|
+
return JSON.parse(raw.content.toString());
|
|
117612
|
+
} catch {
|
|
117613
|
+
return raw.content.toString();
|
|
117614
|
+
}
|
|
117615
|
+
})();
|
|
117616
|
+
const msg = {
|
|
117617
|
+
data,
|
|
117618
|
+
raw,
|
|
117619
|
+
ack: () => channel.ack(raw),
|
|
117620
|
+
nack: (requeue = true) => channel.nack(raw, false, requeue),
|
|
117621
|
+
reject: () => channel.reject(raw, false)
|
|
117622
|
+
};
|
|
117623
|
+
try {
|
|
117624
|
+
await instance[descriptor.methodName](msg);
|
|
117625
|
+
} catch (err) {
|
|
117626
|
+
AppStartup.logger.error(`Unhandled error in RabbitMQ handler ${providerClass.name}.${descriptor.methodName}() on exchange "${exchange}" routingKey "${routingKey}": ${err.message}`);
|
|
117627
|
+
if (!noAck) {
|
|
117628
|
+
channel.nack(raw, false, true);
|
|
117629
|
+
}
|
|
117630
|
+
}
|
|
117631
|
+
}, { noAck });
|
|
117632
|
+
} catch (err) {
|
|
117633
|
+
AppStartup.logger.error(`Failed to register consumer for exchange "${exchange}" routingKey "${routingKey}": ${err.message}`);
|
|
117634
|
+
}
|
|
117635
|
+
continue;
|
|
117636
|
+
}
|
|
117637
|
+
if (!queue2) {
|
|
117638
|
+
AppStartup.logger.warn(`@RabbitSubscribe on ${providerClass.name}.${descriptor.methodName}() has neither 'queue' nor 'exchange'+'routingKey' \u2013 skipping.`);
|
|
117639
|
+
continue;
|
|
117640
|
+
}
|
|
117583
117641
|
AppStartup.logger.log(`Registering RabbitMQ consumer for queue: "${queue2}" \u2192 ${providerClass.name}.${descriptor.methodName}()`);
|
|
117584
117642
|
try {
|
|
117585
117643
|
const channel = await RabbitMQConnection.getConsumerChannel(queue2);
|
|
@@ -120,13 +120,51 @@ export interface RabbitPublishOptions {
|
|
|
120
120
|
}
|
|
121
121
|
/**
|
|
122
122
|
* Options for the `@RabbitSubscribe` method decorator.
|
|
123
|
+
*
|
|
124
|
+
* Two usage modes:
|
|
125
|
+
*
|
|
126
|
+
* 1. **Direct queue** – set `queue` to consume from a named queue.
|
|
127
|
+
* 2. **Routing key** – set `exchange` + `routingKey` to subscribe to a topic/direct
|
|
128
|
+
* exchange with a specific routing key pattern. The lib creates an exclusive
|
|
129
|
+
* auto-delete queue per handler, so **every** handler bound to the same routing
|
|
130
|
+
* key receives its own copy of the message (fan-out per key).
|
|
131
|
+
*
|
|
132
|
+
* @example Queue mode
|
|
133
|
+
* ```typescript
|
|
134
|
+
* @RabbitSubscribe({ queue: 'orders.created' })
|
|
135
|
+
* async handle(msg: RabbitMessage<Order>) { msg.ack(); }
|
|
136
|
+
* ```
|
|
137
|
+
*
|
|
138
|
+
* @example Routing key mode
|
|
139
|
+
* ```typescript
|
|
140
|
+
* @RabbitSubscribe({ exchange: 'events', routingKey: 'article.published' })
|
|
141
|
+
* async onPublished(msg: RabbitMessage<Article>) { msg.ack(); }
|
|
142
|
+
* ```
|
|
123
143
|
*/
|
|
124
144
|
export interface RabbitSubscribeOptions {
|
|
125
145
|
/**
|
|
126
146
|
* Queue to consume messages from.
|
|
127
147
|
* The queue must be declared either via `RabbitMQModule.register({ queues: [...] })` or by the broker.
|
|
148
|
+
* Mutually exclusive with `exchange` + `routingKey`.
|
|
128
149
|
*/
|
|
129
|
-
queue
|
|
150
|
+
queue?: string;
|
|
151
|
+
/**
|
|
152
|
+
* Exchange name to bind to. Must be used together with `routingKey`.
|
|
153
|
+
* The lib automatically creates an exclusive auto-delete queue for each handler
|
|
154
|
+
* and binds it to this exchange, so all handlers for the same routing key
|
|
155
|
+
* receive a copy of every published message.
|
|
156
|
+
*/
|
|
157
|
+
exchange?: string;
|
|
158
|
+
/**
|
|
159
|
+
* Routing key to subscribe to. Supports wildcards for topic exchanges:
|
|
160
|
+
* - `*` matches exactly one word
|
|
161
|
+
* - `#` matches zero or more words
|
|
162
|
+
*
|
|
163
|
+
* Examples: `article.published`, `article.*`, `article.#`
|
|
164
|
+
*
|
|
165
|
+
* Must be used together with `exchange`.
|
|
166
|
+
*/
|
|
167
|
+
routingKey?: string;
|
|
130
168
|
/**
|
|
131
169
|
* When `true`, messages are automatically acknowledged as soon as they are delivered.
|
|
132
170
|
* When `false` (default), you must call `msg.ack()` / `msg.nack()` manually.
|
|
@@ -27,6 +27,20 @@ export declare class RabbitMQConnection {
|
|
|
27
27
|
* Each queue gets its own channel so that prefetch applies per-queue.
|
|
28
28
|
*/
|
|
29
29
|
static getConsumerChannel(queue: string): Promise<Channel>;
|
|
30
|
+
/**
|
|
31
|
+
* Creates a **new** channel with an exclusive, auto-delete server-named queue
|
|
32
|
+
* bound to `exchange` with `routingKey`.
|
|
33
|
+
*
|
|
34
|
+
* Because each call creates an independent queue, every handler that subscribes
|
|
35
|
+
* to the same exchange + routing key receives its own copy of the message
|
|
36
|
+
* (fan-out behaviour per routing key).
|
|
37
|
+
*
|
|
38
|
+
* The channel and queue are **not** cached – each call returns a fresh pair.
|
|
39
|
+
*/
|
|
40
|
+
static createRoutingKeyConsumerChannel(exchange: string, routingKey: string): Promise<{
|
|
41
|
+
channel: Channel;
|
|
42
|
+
queueName: string;
|
|
43
|
+
}>;
|
|
30
44
|
/**
|
|
31
45
|
* Initialises the connection, asserts all configured exchanges and queues,
|
|
32
46
|
* then resolves. Safe to call multiple times – returns the same promise.
|
package/lib/app-startup.ts
CHANGED
|
@@ -886,7 +886,76 @@ if (document.readyState === 'loading') {
|
|
|
886
886
|
const instance = injectables?.get(providerClass) ?? new providerClass();
|
|
887
887
|
|
|
888
888
|
for (const descriptor of descriptors) {
|
|
889
|
-
const {
|
|
889
|
+
const {
|
|
890
|
+
queue,
|
|
891
|
+
exchange,
|
|
892
|
+
routingKey,
|
|
893
|
+
noAck = false,
|
|
894
|
+
} = descriptor.options;
|
|
895
|
+
|
|
896
|
+
// ── Routing-key mode: exchange + routingKey ─────────────────────────
|
|
897
|
+
if (exchange && routingKey) {
|
|
898
|
+
AppStartup.logger.log(
|
|
899
|
+
`Registering RabbitMQ consumer for exchange: "${exchange}" routingKey: "${routingKey}" → ${providerClass.name}.${descriptor.methodName}()`,
|
|
900
|
+
);
|
|
901
|
+
|
|
902
|
+
try {
|
|
903
|
+
const { channel, queueName } =
|
|
904
|
+
await RabbitMQConnection.createRoutingKeyConsumerChannel(
|
|
905
|
+
exchange,
|
|
906
|
+
routingKey,
|
|
907
|
+
);
|
|
908
|
+
|
|
909
|
+
await channel.consume(
|
|
910
|
+
queueName,
|
|
911
|
+
async (raw) => {
|
|
912
|
+
if (!raw) return;
|
|
913
|
+
|
|
914
|
+
const data = (() => {
|
|
915
|
+
try {
|
|
916
|
+
return JSON.parse(raw.content.toString());
|
|
917
|
+
} catch {
|
|
918
|
+
return raw.content.toString();
|
|
919
|
+
}
|
|
920
|
+
})();
|
|
921
|
+
|
|
922
|
+
const msg: RabbitMessage = {
|
|
923
|
+
data,
|
|
924
|
+
raw,
|
|
925
|
+
ack: () => channel.ack(raw),
|
|
926
|
+
nack: (requeue = true) => channel.nack(raw, false, requeue),
|
|
927
|
+
reject: () => channel.reject(raw, false),
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
try {
|
|
931
|
+
await instance[descriptor.methodName](msg);
|
|
932
|
+
} catch (err: any) {
|
|
933
|
+
AppStartup.logger.error(
|
|
934
|
+
`Unhandled error in RabbitMQ handler ${providerClass.name}.${descriptor.methodName}() on exchange "${exchange}" routingKey "${routingKey}": ${err.message}`,
|
|
935
|
+
);
|
|
936
|
+
if (!noAck) {
|
|
937
|
+
channel.nack(raw, false, true);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
},
|
|
941
|
+
{ noAck },
|
|
942
|
+
);
|
|
943
|
+
} catch (err: any) {
|
|
944
|
+
AppStartup.logger.error(
|
|
945
|
+
`Failed to register consumer for exchange "${exchange}" routingKey "${routingKey}": ${err.message}`,
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// ── Direct queue mode ──────────────────────────────────────────────
|
|
953
|
+
if (!queue) {
|
|
954
|
+
AppStartup.logger.warn(
|
|
955
|
+
`@RabbitSubscribe on ${providerClass.name}.${descriptor.methodName}() has neither 'queue' nor 'exchange'+'routingKey' – skipping.`,
|
|
956
|
+
);
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
890
959
|
|
|
891
960
|
AppStartup.logger.log(
|
|
892
961
|
`Registering RabbitMQ consumer for queue: "${queue}" → ${providerClass.name}.${descriptor.methodName}()`,
|
|
@@ -134,13 +134,51 @@ export interface RabbitPublishOptions {
|
|
|
134
134
|
|
|
135
135
|
/**
|
|
136
136
|
* Options for the `@RabbitSubscribe` method decorator.
|
|
137
|
+
*
|
|
138
|
+
* Two usage modes:
|
|
139
|
+
*
|
|
140
|
+
* 1. **Direct queue** – set `queue` to consume from a named queue.
|
|
141
|
+
* 2. **Routing key** – set `exchange` + `routingKey` to subscribe to a topic/direct
|
|
142
|
+
* exchange with a specific routing key pattern. The lib creates an exclusive
|
|
143
|
+
* auto-delete queue per handler, so **every** handler bound to the same routing
|
|
144
|
+
* key receives its own copy of the message (fan-out per key).
|
|
145
|
+
*
|
|
146
|
+
* @example Queue mode
|
|
147
|
+
* ```typescript
|
|
148
|
+
* @RabbitSubscribe({ queue: 'orders.created' })
|
|
149
|
+
* async handle(msg: RabbitMessage<Order>) { msg.ack(); }
|
|
150
|
+
* ```
|
|
151
|
+
*
|
|
152
|
+
* @example Routing key mode
|
|
153
|
+
* ```typescript
|
|
154
|
+
* @RabbitSubscribe({ exchange: 'events', routingKey: 'article.published' })
|
|
155
|
+
* async onPublished(msg: RabbitMessage<Article>) { msg.ack(); }
|
|
156
|
+
* ```
|
|
137
157
|
*/
|
|
138
158
|
export interface RabbitSubscribeOptions {
|
|
139
159
|
/**
|
|
140
160
|
* Queue to consume messages from.
|
|
141
161
|
* The queue must be declared either via `RabbitMQModule.register({ queues: [...] })` or by the broker.
|
|
162
|
+
* Mutually exclusive with `exchange` + `routingKey`.
|
|
142
163
|
*/
|
|
143
|
-
queue
|
|
164
|
+
queue?: string;
|
|
165
|
+
/**
|
|
166
|
+
* Exchange name to bind to. Must be used together with `routingKey`.
|
|
167
|
+
* The lib automatically creates an exclusive auto-delete queue for each handler
|
|
168
|
+
* and binds it to this exchange, so all handlers for the same routing key
|
|
169
|
+
* receive a copy of every published message.
|
|
170
|
+
*/
|
|
171
|
+
exchange?: string;
|
|
172
|
+
/**
|
|
173
|
+
* Routing key to subscribe to. Supports wildcards for topic exchanges:
|
|
174
|
+
* - `*` matches exactly one word
|
|
175
|
+
* - `#` matches zero or more words
|
|
176
|
+
*
|
|
177
|
+
* Examples: `article.published`, `article.*`, `article.#`
|
|
178
|
+
*
|
|
179
|
+
* Must be used together with `exchange`.
|
|
180
|
+
*/
|
|
181
|
+
routingKey?: string;
|
|
144
182
|
/**
|
|
145
183
|
* When `true`, messages are automatically acknowledged as soon as they are delivered.
|
|
146
184
|
* When `false` (default), you must call `msg.ack()` / `msg.nack()` manually.
|
|
@@ -70,6 +70,42 @@ export class RabbitMQConnection {
|
|
|
70
70
|
return channel;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Creates a **new** channel with an exclusive, auto-delete server-named queue
|
|
75
|
+
* bound to `exchange` with `routingKey`.
|
|
76
|
+
*
|
|
77
|
+
* Because each call creates an independent queue, every handler that subscribes
|
|
78
|
+
* to the same exchange + routing key receives its own copy of the message
|
|
79
|
+
* (fan-out behaviour per routing key).
|
|
80
|
+
*
|
|
81
|
+
* The channel and queue are **not** cached – each call returns a fresh pair.
|
|
82
|
+
*/
|
|
83
|
+
static async createRoutingKeyConsumerChannel(
|
|
84
|
+
exchange: string,
|
|
85
|
+
routingKey: string,
|
|
86
|
+
): Promise<{ channel: Channel; queueName: string }> {
|
|
87
|
+
const connection = await RabbitMQConnection.getConnection();
|
|
88
|
+
const channel = await connection.createChannel();
|
|
89
|
+
|
|
90
|
+
const prefetch = RabbitMQConnection.options?.prefetch ?? 10;
|
|
91
|
+
await channel.prefetch(prefetch);
|
|
92
|
+
|
|
93
|
+
// Server-generated name, exclusive so only this consumer uses it,
|
|
94
|
+
// autoDelete so it disappears when the consumer disconnects.
|
|
95
|
+
const { queue: queueName } = await channel.assertQueue("", {
|
|
96
|
+
exclusive: true,
|
|
97
|
+
autoDelete: true,
|
|
98
|
+
durable: false,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
await channel.bindQueue(queueName, exchange, routingKey);
|
|
102
|
+
logger.log(
|
|
103
|
+
`Routing-key consumer queue "${queueName}" bound to exchange "${exchange}" with key "${routingKey}"`,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return { channel, queueName };
|
|
107
|
+
}
|
|
108
|
+
|
|
73
109
|
/**
|
|
74
110
|
* Initialises the connection, asserts all configured exchanges and queues,
|
|
75
111
|
* then resolves. Safe to call multiple times – returns the same promise.
|