@grupodiariodaregiao/bunstone 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +55 -27
- package/dist/lib/app-startup.d.ts +8 -0
- package/lib/app-startup.ts +94 -37
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -117590,6 +117590,7 @@ if (document.readyState === 'loading') {
|
|
|
117590
117590
|
AppStartup.logger.error(`RabbitMQ initialisation failed: ${err.message}`);
|
|
117591
117591
|
return;
|
|
117592
117592
|
}
|
|
117593
|
+
const queueMap = new Map;
|
|
117593
117594
|
for (const [providerClass, descriptors] of providersRabbitMQ.entries()) {
|
|
117594
117595
|
const instance = injectables?.get(providerClass) ?? new providerClass;
|
|
117595
117596
|
for (const descriptor of descriptors) {
|
|
@@ -117638,38 +117639,65 @@ if (document.readyState === 'loading') {
|
|
|
117638
117639
|
AppStartup.logger.warn(`@RabbitSubscribe on ${providerClass.name}.${descriptor.methodName}() has neither 'queue' nor 'exchange'+'routingKey' \u2013 skipping.`);
|
|
117639
117640
|
continue;
|
|
117640
117641
|
}
|
|
117641
|
-
|
|
117642
|
-
|
|
117643
|
-
|
|
117644
|
-
|
|
117645
|
-
|
|
117646
|
-
|
|
117647
|
-
|
|
117648
|
-
|
|
117649
|
-
|
|
117650
|
-
|
|
117651
|
-
|
|
117652
|
-
|
|
117653
|
-
|
|
117654
|
-
|
|
117655
|
-
|
|
117656
|
-
|
|
117657
|
-
|
|
117658
|
-
|
|
117659
|
-
|
|
117660
|
-
|
|
117642
|
+
if (!queueMap.has(queue2))
|
|
117643
|
+
queueMap.set(queue2, []);
|
|
117644
|
+
const queueHandlers = queueMap.get(queue2);
|
|
117645
|
+
queueHandlers.push({
|
|
117646
|
+
instance,
|
|
117647
|
+
descriptor,
|
|
117648
|
+
noAck,
|
|
117649
|
+
providerName: providerClass.name
|
|
117650
|
+
});
|
|
117651
|
+
}
|
|
117652
|
+
}
|
|
117653
|
+
for (const [queue2, handlers] of queueMap.entries()) {
|
|
117654
|
+
const noAck = handlers.every((h3) => h3.noAck);
|
|
117655
|
+
const handlerList = handlers.map((h3) => `${h3.providerName}.${h3.descriptor.methodName}()`).join(", ");
|
|
117656
|
+
AppStartup.logger.log(`Registering RabbitMQ consumer for queue: "${queue2}" \u2192 [${handlerList}]`);
|
|
117657
|
+
try {
|
|
117658
|
+
const channel = await RabbitMQConnection.getConsumerChannel(queue2);
|
|
117659
|
+
await channel.consume(queue2, async (raw) => {
|
|
117660
|
+
if (!raw)
|
|
117661
|
+
return;
|
|
117662
|
+
const data = (() => {
|
|
117663
|
+
try {
|
|
117664
|
+
return JSON.parse(raw.content.toString());
|
|
117665
|
+
} catch {
|
|
117666
|
+
return raw.content.toString();
|
|
117667
|
+
}
|
|
117668
|
+
})();
|
|
117669
|
+
let settled = false;
|
|
117670
|
+
const settle = (fn3) => {
|
|
117671
|
+
if (!settled) {
|
|
117672
|
+
settled = true;
|
|
117673
|
+
fn3();
|
|
117674
|
+
}
|
|
117675
|
+
};
|
|
117676
|
+
const msg = {
|
|
117677
|
+
data,
|
|
117678
|
+
raw,
|
|
117679
|
+
ack: () => settle(() => channel.ack(raw)),
|
|
117680
|
+
nack: (requeue = true) => settle(() => channel.nack(raw, false, requeue)),
|
|
117681
|
+
reject: () => settle(() => channel.reject(raw, false))
|
|
117682
|
+
};
|
|
117683
|
+
for (const {
|
|
117684
|
+
instance,
|
|
117685
|
+
descriptor,
|
|
117686
|
+
noAck: handlerNoAck,
|
|
117687
|
+
providerName
|
|
117688
|
+
} of handlers) {
|
|
117661
117689
|
try {
|
|
117662
117690
|
await instance[descriptor.methodName](msg);
|
|
117663
117691
|
} catch (err) {
|
|
117664
|
-
AppStartup.logger.error(`Unhandled error in RabbitMQ handler ${
|
|
117665
|
-
if (!
|
|
117666
|
-
channel.nack(raw, false, true);
|
|
117692
|
+
AppStartup.logger.error(`Unhandled error in RabbitMQ handler ${providerName}.${descriptor.methodName}() on queue "${queue2}": ${err.message}`);
|
|
117693
|
+
if (!handlerNoAck && !settled) {
|
|
117694
|
+
settle(() => channel.nack(raw, false, true));
|
|
117667
117695
|
}
|
|
117668
117696
|
}
|
|
117669
|
-
}
|
|
117670
|
-
}
|
|
117671
|
-
|
|
117672
|
-
}
|
|
117697
|
+
}
|
|
117698
|
+
}, { noAck });
|
|
117699
|
+
} catch (err) {
|
|
117700
|
+
AppStartup.logger.error(`Failed to register consumer for queue "${queue2}": ${err.message}`);
|
|
117673
117701
|
}
|
|
117674
117702
|
}
|
|
117675
117703
|
})();
|
|
@@ -80,6 +80,14 @@ export declare class AppStartup {
|
|
|
80
80
|
/**
|
|
81
81
|
* Sets up RabbitMQ consumers for every provider that has `@RabbitSubscribe`
|
|
82
82
|
* methods registered in the given module.
|
|
83
|
+
*
|
|
84
|
+
* Queue mode (fan-out): all handlers subscribed to the same named queue share a
|
|
85
|
+
* single AMQP consumer. Every message is delivered to ALL handlers in declaration
|
|
86
|
+
* order. A "settle guard" ensures ack/nack/reject is called only once per message
|
|
87
|
+
* regardless of how many handlers invoke it.
|
|
88
|
+
*
|
|
89
|
+
* Routing-key mode: each handler gets its own exclusive auto-delete queue bound
|
|
90
|
+
* to the exchange, so the broker itself handles fan-out.
|
|
83
91
|
*/
|
|
84
92
|
private static registerRabbitMQConsumers;
|
|
85
93
|
private static registerCqrsHandlers;
|
package/lib/app-startup.ts
CHANGED
|
@@ -857,6 +857,14 @@ if (document.readyState === 'loading') {
|
|
|
857
857
|
/**
|
|
858
858
|
* Sets up RabbitMQ consumers for every provider that has `@RabbitSubscribe`
|
|
859
859
|
* methods registered in the given module.
|
|
860
|
+
*
|
|
861
|
+
* Queue mode (fan-out): all handlers subscribed to the same named queue share a
|
|
862
|
+
* single AMQP consumer. Every message is delivered to ALL handlers in declaration
|
|
863
|
+
* order. A "settle guard" ensures ack/nack/reject is called only once per message
|
|
864
|
+
* regardless of how many handlers invoke it.
|
|
865
|
+
*
|
|
866
|
+
* Routing-key mode: each handler gets its own exclusive auto-delete queue bound
|
|
867
|
+
* to the exchange, so the broker itself handles fan-out.
|
|
860
868
|
*/
|
|
861
869
|
private static registerRabbitMQConsumers(module: any): void {
|
|
862
870
|
const providersRabbitMQ: Map<any, RabbitMQMethodDescriptor[]> | undefined =
|
|
@@ -871,6 +879,13 @@ if (document.readyState === 'loading') {
|
|
|
871
879
|
module,
|
|
872
880
|
);
|
|
873
881
|
|
|
882
|
+
type QueueHandler = {
|
|
883
|
+
instance: any;
|
|
884
|
+
descriptor: RabbitMQMethodDescriptor;
|
|
885
|
+
noAck: boolean;
|
|
886
|
+
providerName: string;
|
|
887
|
+
};
|
|
888
|
+
|
|
874
889
|
// Fire-and-forget – connect asynchronously so startup is never blocked
|
|
875
890
|
(async () => {
|
|
876
891
|
try {
|
|
@@ -882,6 +897,12 @@ if (document.readyState === 'loading') {
|
|
|
882
897
|
return;
|
|
883
898
|
}
|
|
884
899
|
|
|
900
|
+
// ── Step 1: separate routing-key handlers from named-queue handlers ──
|
|
901
|
+
// Named-queue handlers are grouped by queue name so that a single AMQP
|
|
902
|
+
// consumer is created per queue and every message is fanned-out to all
|
|
903
|
+
// registered handlers in-process.
|
|
904
|
+
const queueMap = new Map<string, QueueHandler[]>();
|
|
905
|
+
|
|
885
906
|
for (const [providerClass, descriptors] of providersRabbitMQ.entries()) {
|
|
886
907
|
const instance = injectables?.get(providerClass) ?? new providerClass();
|
|
887
908
|
|
|
@@ -893,7 +914,7 @@ if (document.readyState === 'loading') {
|
|
|
893
914
|
noAck = false,
|
|
894
915
|
} = descriptor.options;
|
|
895
916
|
|
|
896
|
-
// ── Routing-key mode: exchange + routingKey
|
|
917
|
+
// ── Routing-key mode: exchange + routingKey ─────────────────────
|
|
897
918
|
if (exchange && routingKey) {
|
|
898
919
|
AppStartup.logger.log(
|
|
899
920
|
`Registering RabbitMQ consumer for exchange: "${exchange}" routingKey: "${routingKey}" → ${providerClass.name}.${descriptor.methodName}()`,
|
|
@@ -949,7 +970,7 @@ if (document.readyState === 'loading') {
|
|
|
949
970
|
continue;
|
|
950
971
|
}
|
|
951
972
|
|
|
952
|
-
// ──
|
|
973
|
+
// ── Queue mode: collect and group by queue name ─────────────────
|
|
953
974
|
if (!queue) {
|
|
954
975
|
AppStartup.logger.warn(
|
|
955
976
|
`@RabbitSubscribe on ${providerClass.name}.${descriptor.methodName}() has neither 'queue' nor 'exchange'+'routingKey' – skipping.`,
|
|
@@ -957,53 +978,89 @@ if (document.readyState === 'loading') {
|
|
|
957
978
|
continue;
|
|
958
979
|
}
|
|
959
980
|
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
981
|
+
if (!queueMap.has(queue)) queueMap.set(queue, []);
|
|
982
|
+
const queueHandlers = queueMap.get(queue) as QueueHandler[];
|
|
983
|
+
queueHandlers.push({
|
|
984
|
+
instance,
|
|
985
|
+
descriptor,
|
|
986
|
+
noAck,
|
|
987
|
+
providerName: providerClass.name,
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
}
|
|
963
991
|
|
|
964
|
-
|
|
965
|
-
|
|
992
|
+
// ── Step 2: one AMQP consumer per unique queue, fan-out in-process ──
|
|
993
|
+
for (const [queue, handlers] of queueMap.entries()) {
|
|
994
|
+
// Use noAck only when every handler opts in; otherwise manual ack.
|
|
995
|
+
const noAck = handlers.every((h) => h.noAck);
|
|
966
996
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
if (!raw) return; // consumer cancelled
|
|
997
|
+
const handlerList = handlers
|
|
998
|
+
.map((h) => `${h.providerName}.${h.descriptor.methodName}()`)
|
|
999
|
+
.join(", ");
|
|
971
1000
|
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
} catch {
|
|
976
|
-
return raw.content.toString();
|
|
977
|
-
}
|
|
978
|
-
})();
|
|
1001
|
+
AppStartup.logger.log(
|
|
1002
|
+
`Registering RabbitMQ consumer for queue: "${queue}" → [${handlerList}]`,
|
|
1003
|
+
);
|
|
979
1004
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
raw,
|
|
983
|
-
ack: () => channel.ack(raw),
|
|
984
|
-
nack: (requeue = true) => channel.nack(raw, false, requeue),
|
|
985
|
-
reject: () => channel.reject(raw, false),
|
|
986
|
-
};
|
|
1005
|
+
try {
|
|
1006
|
+
const channel = await RabbitMQConnection.getConsumerChannel(queue);
|
|
987
1007
|
|
|
1008
|
+
await channel.consume(
|
|
1009
|
+
queue,
|
|
1010
|
+
async (raw) => {
|
|
1011
|
+
if (!raw) return; // consumer cancelled
|
|
1012
|
+
|
|
1013
|
+
const data = (() => {
|
|
1014
|
+
try {
|
|
1015
|
+
return JSON.parse(raw.content.toString());
|
|
1016
|
+
} catch {
|
|
1017
|
+
return raw.content.toString();
|
|
1018
|
+
}
|
|
1019
|
+
})();
|
|
1020
|
+
|
|
1021
|
+
// Settle guard: ack/nack/reject may only be called once per
|
|
1022
|
+
// delivery tag regardless of how many handlers invoke it.
|
|
1023
|
+
let settled = false;
|
|
1024
|
+
const settle = (fn: () => void) => {
|
|
1025
|
+
if (!settled) {
|
|
1026
|
+
settled = true;
|
|
1027
|
+
fn();
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
const msg: RabbitMessage = {
|
|
1032
|
+
data,
|
|
1033
|
+
raw,
|
|
1034
|
+
ack: () => settle(() => channel.ack(raw)),
|
|
1035
|
+
nack: (requeue = true) =>
|
|
1036
|
+
settle(() => channel.nack(raw, false, requeue)),
|
|
1037
|
+
reject: () => settle(() => channel.reject(raw, false)),
|
|
1038
|
+
};
|
|
1039
|
+
|
|
1040
|
+
for (const {
|
|
1041
|
+
instance,
|
|
1042
|
+
descriptor,
|
|
1043
|
+
noAck: handlerNoAck,
|
|
1044
|
+
providerName,
|
|
1045
|
+
} of handlers) {
|
|
988
1046
|
try {
|
|
989
1047
|
await instance[descriptor.methodName](msg);
|
|
990
1048
|
} catch (err: any) {
|
|
991
1049
|
AppStartup.logger.error(
|
|
992
|
-
`Unhandled error in RabbitMQ handler ${
|
|
1050
|
+
`Unhandled error in RabbitMQ handler ${providerName}.${descriptor.methodName}() on queue "${queue}": ${err.message}`,
|
|
993
1051
|
);
|
|
994
|
-
if (!
|
|
995
|
-
|
|
996
|
-
channel.nack(raw, false, true);
|
|
1052
|
+
if (!handlerNoAck && !settled) {
|
|
1053
|
+
settle(() => channel.nack(raw, false, true));
|
|
997
1054
|
}
|
|
998
1055
|
}
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1056
|
+
}
|
|
1057
|
+
},
|
|
1058
|
+
{ noAck },
|
|
1059
|
+
);
|
|
1060
|
+
} catch (err: any) {
|
|
1061
|
+
AppStartup.logger.error(
|
|
1062
|
+
`Failed to register consumer for queue "${queue}": ${err.message}`,
|
|
1063
|
+
);
|
|
1007
1064
|
}
|
|
1008
1065
|
}
|
|
1009
1066
|
})();
|