@grupodiariodaregiao/bunstone 0.3.11 → 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 +65 -4
- 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 +75 -2
- package/lib/http-exceptions.ts +4 -9
- 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
|
@@ -99813,8 +99813,8 @@ class HttpException extends Error {
|
|
|
99813
99813
|
response;
|
|
99814
99814
|
constructor(response, status) {
|
|
99815
99815
|
const responseObj = typeof response === "string" ? { message: response } : response;
|
|
99816
|
-
const
|
|
99817
|
-
super(
|
|
99816
|
+
const message = typeof response === "string" ? response : response.message ?? JSON.stringify(response);
|
|
99817
|
+
super(message);
|
|
99818
99818
|
this.status = status;
|
|
99819
99819
|
this.response = responseObj;
|
|
99820
99820
|
Object.setPrototypeOf(this, HttpException.prototype);
|
|
@@ -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
|
}
|
|
@@ -117125,7 +117139,10 @@ class AppStartup {
|
|
|
117125
117139
|
errors: errors5
|
|
117126
117140
|
};
|
|
117127
117141
|
}
|
|
117128
|
-
|
|
117142
|
+
set2.status = 500;
|
|
117143
|
+
return {
|
|
117144
|
+
message: error48 instanceof Error ? error48.message : "Internal Server Error"
|
|
117145
|
+
};
|
|
117129
117146
|
});
|
|
117130
117147
|
if (options?.cors) {
|
|
117131
117148
|
AppStartup.elysia.use(cors(options.cors));
|
|
@@ -117576,7 +117593,51 @@ if (document.readyState === 'loading') {
|
|
|
117576
117593
|
for (const [providerClass, descriptors] of providersRabbitMQ.entries()) {
|
|
117577
117594
|
const instance = injectables?.get(providerClass) ?? new providerClass;
|
|
117578
117595
|
for (const descriptor of descriptors) {
|
|
117579
|
-
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
|
+
}
|
|
117580
117641
|
AppStartup.logger.log(`Registering RabbitMQ consumer for queue: "${queue2}" \u2192 ${providerClass.name}.${descriptor.methodName}()`);
|
|
117581
117642
|
try {
|
|
117582
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
|
@@ -142,7 +142,11 @@ export class AppStartup {
|
|
|
142
142
|
};
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
set.status = 500;
|
|
146
|
+
return {
|
|
147
|
+
message:
|
|
148
|
+
error instanceof Error ? error.message : "Internal Server Error",
|
|
149
|
+
};
|
|
146
150
|
});
|
|
147
151
|
|
|
148
152
|
if (options?.cors) {
|
|
@@ -882,7 +886,76 @@ if (document.readyState === 'loading') {
|
|
|
882
886
|
const instance = injectables?.get(providerClass) ?? new providerClass();
|
|
883
887
|
|
|
884
888
|
for (const descriptor of descriptors) {
|
|
885
|
-
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
|
+
}
|
|
886
959
|
|
|
887
960
|
AppStartup.logger.log(
|
|
888
961
|
`Registering RabbitMQ consumer for queue: "${queue}" → ${providerClass.name}.${descriptor.methodName}()`,
|
package/lib/http-exceptions.ts
CHANGED
|
@@ -10,17 +10,12 @@ export class HttpException extends Error {
|
|
|
10
10
|
) {
|
|
11
11
|
const responseObj =
|
|
12
12
|
typeof response === "string" ? { message: response } : response;
|
|
13
|
-
|
|
14
|
-
// to avoid JSON.stringify issues when error is serialized
|
|
15
|
-
const errorMessage =
|
|
13
|
+
const message =
|
|
16
14
|
typeof response === "string"
|
|
17
15
|
? response
|
|
18
|
-
:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
? String(response.message)
|
|
22
|
-
: "Error";
|
|
23
|
-
super(errorMessage);
|
|
16
|
+
: ((response as { message?: string }).message ??
|
|
17
|
+
JSON.stringify(response));
|
|
18
|
+
super(message);
|
|
24
19
|
this.response = responseObj;
|
|
25
20
|
Object.setPrototypeOf(this, HttpException.prototype);
|
|
26
21
|
}
|
|
@@ -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.
|