@zdavison/matador 4.0.2 → 4.0.3
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/core/fanout.d.ts +51 -0
- package/dist/core/fanout.d.ts.map +1 -1
- package/dist/core/fanout.js +141 -28
- package/dist/core/fanout.test.js +533 -7
- package/dist/core/matador.d.ts +33 -0
- package/dist/core/matador.d.ts.map +1 -1
- package/dist/core/matador.js +5 -1
- package/dist/core/matador.test.js +242 -6
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +2 -2
- package/dist/errors/matador-errors.d.ts +10 -8
- package/dist/errors/matador-errors.d.ts.map +1 -1
- package/dist/errors/matador-errors.js +18 -14
- package/dist/index.cjs +215 -82
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/transport/local/local-transport.d.ts +6 -0
- package/dist/transport/local/local-transport.d.ts.map +1 -1
- package/dist/transport/local/local-transport.js +35 -18
- package/dist/transport/local/local-transport.test.js +40 -4
- package/dist/transport/multi/multi-transport.d.ts.map +1 -1
- package/dist/transport/multi/multi-transport.js +3 -0
- package/dist/transport/multi/multi-transport.test.js +82 -0
- package/dist/transport/rabbitmq/rabbitmq-transport.d.ts.map +1 -1
- package/dist/transport/rabbitmq/rabbitmq-transport.js +31 -27
- package/dist/transport/transport.d.ts +6 -0
- package/dist/transport/transport.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -202,6 +202,16 @@ var DelayedMessagesNotSupportedError = class extends MatadorError {
|
|
|
202
202
|
transportName;
|
|
203
203
|
description = "Delayed messages were requested but the transport does not support them. ACTION: For RabbitMQ, install the rabbitmq_delayed_message_exchange plugin. Run: rabbitmq-plugins enable rabbitmq_delayed_message_exchange Then restart RabbitMQ and reconnect. Alternatively, remove delayMs from your event options if delays are not required.";
|
|
204
204
|
};
|
|
205
|
+
var LocalTransportNoActiveSubscriberError = class extends MatadorError {
|
|
206
|
+
constructor(queue) {
|
|
207
|
+
super(
|
|
208
|
+
`LocalTransport has no active subscriber for queue "${queue}"; message cannot be delivered.`
|
|
209
|
+
);
|
|
210
|
+
this.queue = queue;
|
|
211
|
+
}
|
|
212
|
+
queue;
|
|
213
|
+
description = "The LocalTransport has no active subscriber for this queue in the current process, so it cannot guarantee delivery. ACTION: If the subscriber is a SubscriberStub (implemented in another service), the local transport can never deliver it locally \u2014 a distributed transport like RabbitMQ is required. If the subscriber should run in this process, ensure matador.start() has subscribed to this queue before sending.";
|
|
214
|
+
};
|
|
205
215
|
var EventNotRegisteredError = class extends MatadorError {
|
|
206
216
|
constructor(eventKey) {
|
|
207
217
|
super(
|
|
@@ -252,16 +262,6 @@ var SubscriberIsStubError = class extends MatadorError {
|
|
|
252
262
|
subscriberName;
|
|
253
263
|
description = "A SubscriberStub was registered in a consuming schema. ACTION: SubscriberStubs should only be used in producer schemas to declare that a subscriber exists in another service. In the consumer service, provide a full Subscriber with a callback function. Remove the stub from the consumer schema and add the actual implementation.";
|
|
254
264
|
};
|
|
255
|
-
var LocalTransportCannotProcessStubError = class extends MatadorError {
|
|
256
|
-
constructor(subscriberName) {
|
|
257
|
-
super(
|
|
258
|
-
`LocalTransport cannot process stub subscriber "${subscriberName}". Stub subscribers require a distributed transport like RabbitMQ.`
|
|
259
|
-
);
|
|
260
|
-
this.subscriberName = subscriberName;
|
|
261
|
-
}
|
|
262
|
-
subscriberName;
|
|
263
|
-
description = "The LocalTransport cannot process events for SubscriberStubs. ACTION: SubscriberStubs represent remote implementations that only RabbitMQ can route. If using LocalTransport for testing, provide mock implementations instead of stubs. For production fallback scenarios, be aware that stub-targeted events will be dropped.";
|
|
264
|
-
};
|
|
265
265
|
var UnknownQueueReferenceError = class extends MatadorError {
|
|
266
266
|
constructor(queueName) {
|
|
267
267
|
super(
|
|
@@ -801,6 +801,7 @@ function createSubscriberStub(input) {
|
|
|
801
801
|
}
|
|
802
802
|
|
|
803
803
|
// src/core/fanout.ts
|
|
804
|
+
var DEFAULT_MAX_CONSECUTIVE_FLUSH_FAILURES = 10;
|
|
804
805
|
var FanoutEngine = class {
|
|
805
806
|
transport;
|
|
806
807
|
schema;
|
|
@@ -808,9 +809,13 @@ var FanoutEngine = class {
|
|
|
808
809
|
topology;
|
|
809
810
|
defaultQueue;
|
|
810
811
|
enqueuingCount = 0;
|
|
812
|
+
flushInFlightCount = 0;
|
|
811
813
|
retryBuffer = [];
|
|
812
814
|
maxRetryBufferSize;
|
|
815
|
+
maxRetryAttempts;
|
|
816
|
+
maxConsecutiveFlushFailures;
|
|
813
817
|
disposeOnConnected;
|
|
818
|
+
retryTimer;
|
|
814
819
|
constructor(config) {
|
|
815
820
|
this.transport = config.transport;
|
|
816
821
|
this.schema = config.schema;
|
|
@@ -818,18 +823,29 @@ var FanoutEngine = class {
|
|
|
818
823
|
this.topology = config.topology;
|
|
819
824
|
this.defaultQueue = config.defaultQueue;
|
|
820
825
|
this.maxRetryBufferSize = config.maxRetryBufferSize ?? 5e3;
|
|
826
|
+
this.maxRetryAttempts = config.maxRetryAttempts;
|
|
827
|
+
const maxConsecutiveFlushFailures = config.maxConsecutiveFlushFailures ?? DEFAULT_MAX_CONSECUTIVE_FLUSH_FAILURES;
|
|
828
|
+
this.maxConsecutiveFlushFailures = maxConsecutiveFlushFailures > 0 ? maxConsecutiveFlushFailures : Number.POSITIVE_INFINITY;
|
|
821
829
|
this.disposeOnConnected = this.transport.onConnected?.(() => {
|
|
822
830
|
void this.flushRetryBuffer();
|
|
823
831
|
});
|
|
832
|
+
const retryIntervalMs = config.retryIntervalMs ?? 3e4;
|
|
833
|
+
if (retryIntervalMs > 0) {
|
|
834
|
+
this.retryTimer = setInterval(() => {
|
|
835
|
+
void this.flushRetryBuffer();
|
|
836
|
+
}, retryIntervalMs);
|
|
837
|
+
this.retryTimer.unref?.();
|
|
838
|
+
}
|
|
824
839
|
}
|
|
825
840
|
dispose() {
|
|
826
841
|
this.disposeOnConnected?.();
|
|
842
|
+
clearInterval(this.retryTimer);
|
|
827
843
|
}
|
|
828
844
|
/**
|
|
829
845
|
* Current count of events being enqueued.
|
|
830
846
|
*/
|
|
831
847
|
get eventsBeingEnqueuedCount() {
|
|
832
|
-
return this.enqueuingCount;
|
|
848
|
+
return this.enqueuingCount + this.flushInFlightCount;
|
|
833
849
|
}
|
|
834
850
|
/**
|
|
835
851
|
* Sends an event to all registered subscribers.
|
|
@@ -883,10 +899,17 @@ var FanoutEngine = class {
|
|
|
883
899
|
queue: qualifiedQueue,
|
|
884
900
|
envelope,
|
|
885
901
|
sendOptions,
|
|
886
|
-
subscriberName: subscriber.name
|
|
902
|
+
subscriberName: subscriber.name,
|
|
903
|
+
attempts: 0
|
|
887
904
|
});
|
|
888
905
|
this.hooks.logger.warn(
|
|
889
|
-
`[Matador] \u{1F7E1} Message for '${subscriber.name}' buffered for retry on reconnect
|
|
906
|
+
`[Matador] \u{1F7E1} Message for '${subscriber.name}' buffered for retry on reconnect.`,
|
|
907
|
+
{
|
|
908
|
+
queue: qualifiedQueue,
|
|
909
|
+
subscriberName: subscriber.name,
|
|
910
|
+
bufferSize: this.retryBuffer.length,
|
|
911
|
+
maxBufferSize: this.maxRetryBufferSize
|
|
912
|
+
}
|
|
890
913
|
);
|
|
891
914
|
if (options.throwOnBufferedFailure) {
|
|
892
915
|
const err = new TransportSendError(qualifiedQueue, cause);
|
|
@@ -930,6 +953,31 @@ var FanoutEngine = class {
|
|
|
930
953
|
errors
|
|
931
954
|
};
|
|
932
955
|
}
|
|
956
|
+
/**
|
|
957
|
+
* Attempts a single buffered item during a flush pass
|
|
958
|
+
* @returns true if the attempt succeeded; false on failure
|
|
959
|
+
*/
|
|
960
|
+
async attemptFlushItem(item) {
|
|
961
|
+
this.enqueuingCount++;
|
|
962
|
+
try {
|
|
963
|
+
const usedTransport = await this.transport.send(
|
|
964
|
+
item.queue,
|
|
965
|
+
item.envelope,
|
|
966
|
+
item.sendOptions
|
|
967
|
+
);
|
|
968
|
+
await this.hooks.onEnqueueSuccess({
|
|
969
|
+
envelope: item.envelope,
|
|
970
|
+
queue: item.queue,
|
|
971
|
+
transport: usedTransport
|
|
972
|
+
});
|
|
973
|
+
return true;
|
|
974
|
+
} catch (error) {
|
|
975
|
+
await this.handleFlushFailure(item, error);
|
|
976
|
+
return false;
|
|
977
|
+
} finally {
|
|
978
|
+
this.enqueuingCount--;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
933
981
|
/**
|
|
934
982
|
* Flushes the retry buffer
|
|
935
983
|
*
|
|
@@ -941,39 +989,36 @@ var FanoutEngine = class {
|
|
|
941
989
|
`[Matador] \u23F3 Flushing ${this.retryBuffer.length} buffered message(s)...`
|
|
942
990
|
);
|
|
943
991
|
const toFlush = this.retryBuffer.splice(0);
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
if (
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
error: err,
|
|
966
|
-
transport: this.transport.name
|
|
967
|
-
});
|
|
992
|
+
this.flushInFlightCount += toFlush.length;
|
|
993
|
+
let consecutiveFailures = 0;
|
|
994
|
+
try {
|
|
995
|
+
for (let i = 0; i < toFlush.length; i++) {
|
|
996
|
+
const item = toFlush[i];
|
|
997
|
+
if (!item) {
|
|
998
|
+
this.flushInFlightCount--;
|
|
999
|
+
continue;
|
|
1000
|
+
}
|
|
1001
|
+
const succeeded = await this.attemptFlushItem(item);
|
|
1002
|
+
this.flushInFlightCount--;
|
|
1003
|
+
const randomMs = Math.random() * 10;
|
|
1004
|
+
await new Promise((resolve) => setTimeout(resolve, randomMs));
|
|
1005
|
+
consecutiveFailures = succeeded ? 0 : consecutiveFailures + 1;
|
|
1006
|
+
if (consecutiveFailures >= this.maxConsecutiveFlushFailures) {
|
|
1007
|
+
const untried = toFlush.slice(i + 1);
|
|
1008
|
+
this.flushInFlightCount -= untried.length;
|
|
1009
|
+
if (untried.length > 0) {
|
|
1010
|
+
await this.rebufferUntried(untried, consecutiveFailures);
|
|
1011
|
+
}
|
|
1012
|
+
break;
|
|
968
1013
|
}
|
|
969
|
-
} finally {
|
|
970
|
-
this.enqueuingCount--;
|
|
971
1014
|
}
|
|
1015
|
+
} finally {
|
|
1016
|
+
this.flushInFlightCount = 0;
|
|
972
1017
|
}
|
|
973
1018
|
const remaining = this.retryBuffer.length;
|
|
974
1019
|
if (remaining > 0) {
|
|
975
1020
|
this.hooks.logger.warn(
|
|
976
|
-
`[Matador] \u{1F7E1} ${remaining} buffered message(s) could not be flushed; will retry
|
|
1021
|
+
`[Matador] \u{1F7E1} ${remaining} buffered message(s) could not be flushed; will retry later.`
|
|
977
1022
|
);
|
|
978
1023
|
} else {
|
|
979
1024
|
this.hooks.logger.info(
|
|
@@ -981,6 +1026,67 @@ var FanoutEngine = class {
|
|
|
981
1026
|
);
|
|
982
1027
|
}
|
|
983
1028
|
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Re-buffers messages that were never attempted because a flush pass
|
|
1031
|
+
* stopped out early. Respects maxRetryBufferSize: concurrent sends can have
|
|
1032
|
+
* refilled the buffer while this flush was running, so anything beyond
|
|
1033
|
+
* remaining capacity is dropped and reported instead of silently growing
|
|
1034
|
+
* the buffer past its cap.
|
|
1035
|
+
*/
|
|
1036
|
+
async rebufferUntried(untried, consecutiveFailures) {
|
|
1037
|
+
const capacity = Math.max(
|
|
1038
|
+
0,
|
|
1039
|
+
this.maxRetryBufferSize - this.retryBuffer.length
|
|
1040
|
+
);
|
|
1041
|
+
const toRebuffer = untried.slice(0, capacity);
|
|
1042
|
+
const dropped = untried.slice(capacity);
|
|
1043
|
+
if (toRebuffer.length > 0) {
|
|
1044
|
+
this.retryBuffer.unshift(...toRebuffer);
|
|
1045
|
+
}
|
|
1046
|
+
const droppedSuffix = dropped.length > 0 ? `, ${dropped.length} dropped (buffer full).` : ".";
|
|
1047
|
+
this.hooks.logger.warn(
|
|
1048
|
+
`[Matador] \u{1F7E1} Stopping this flush pass after ${consecutiveFailures} consecutive failures; ${toRebuffer.length} untried message(s) re-buffered for the next attempt${droppedSuffix}`
|
|
1049
|
+
);
|
|
1050
|
+
for (const item of dropped) {
|
|
1051
|
+
this.hooks.logger.error(
|
|
1052
|
+
`[Matador] \u{1F534} Retry buffer full (${this.maxRetryBufferSize}). Message for '${item.subscriberName}' dropped and will not be retried.`
|
|
1053
|
+
);
|
|
1054
|
+
const err = new TransportSendError(
|
|
1055
|
+
item.queue,
|
|
1056
|
+
new Error("Retry buffer full")
|
|
1057
|
+
);
|
|
1058
|
+
await this.hooks.onEnqueueError({
|
|
1059
|
+
envelope: item.envelope,
|
|
1060
|
+
error: err,
|
|
1061
|
+
transport: this.transport.name
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Handles a failed flush attempt for a single buffered item: drops it once
|
|
1067
|
+
* maxRetryAttempts is exceeded, otherwise re-buffers it (unless the buffer
|
|
1068
|
+
* is full, in which case it's dropped too).
|
|
1069
|
+
*/
|
|
1070
|
+
async handleFlushFailure(item, error) {
|
|
1071
|
+
item.attempts++;
|
|
1072
|
+
const cause = error instanceof Error ? error : new Error(String(error));
|
|
1073
|
+
const exceededAttempts = this.maxRetryAttempts !== void 0 && item.attempts >= this.maxRetryAttempts;
|
|
1074
|
+
if (!exceededAttempts && this.retryBuffer.length < this.maxRetryBufferSize) {
|
|
1075
|
+
this.retryBuffer.push(item);
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
if (exceededAttempts) {
|
|
1079
|
+
this.hooks.logger.error(
|
|
1080
|
+
`[Matador] \u{1F534} Message for '${item.subscriberName}' exceeded max retry attempts (${this.maxRetryAttempts}) and will not be retried further.`
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
const err = new TransportSendError(item.queue, cause);
|
|
1084
|
+
await this.hooks.onEnqueueError({
|
|
1085
|
+
envelope: item.envelope,
|
|
1086
|
+
error: err,
|
|
1087
|
+
transport: this.transport.name
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
984
1090
|
async isSubscriberEnabled(subscriber) {
|
|
985
1091
|
if (!subscriber.enabled) {
|
|
986
1092
|
return true;
|
|
@@ -2273,7 +2379,11 @@ var Matador = class {
|
|
|
2273
2379
|
schema: this.schema,
|
|
2274
2380
|
hooks: this.hooks,
|
|
2275
2381
|
topology: this.topology,
|
|
2276
|
-
defaultQueue
|
|
2382
|
+
defaultQueue,
|
|
2383
|
+
maxRetryBufferSize: config.maxRetryBufferSize,
|
|
2384
|
+
maxRetryAttempts: config.maxRetryAttempts,
|
|
2385
|
+
retryIntervalMs: config.retryIntervalMs,
|
|
2386
|
+
maxConsecutiveFlushFailures: config.maxConsecutiveFlushFailures
|
|
2277
2387
|
});
|
|
2278
2388
|
this.shutdownManager = new ShutdownManager(
|
|
2279
2389
|
() => this.fanout.eventsBeingEnqueuedCount,
|
|
@@ -2459,7 +2569,7 @@ var Matador = class {
|
|
|
2459
2569
|
}
|
|
2460
2570
|
async unsubscribeAll() {
|
|
2461
2571
|
for (const subscription of this.subscriptions) {
|
|
2462
|
-
await subscription.unsubscribe();
|
|
2572
|
+
await (subscription.pauseForShutdown ?? subscription.unsubscribe)();
|
|
2463
2573
|
}
|
|
2464
2574
|
this.subscriptions.length = 0;
|
|
2465
2575
|
}
|
|
@@ -2621,6 +2731,7 @@ var localCapabilities = {
|
|
|
2621
2731
|
ordering: "queue",
|
|
2622
2732
|
priorities: false
|
|
2623
2733
|
};
|
|
2734
|
+
var MAX_COMPLETED_MESSAGES = 1e3;
|
|
2624
2735
|
var LocalTransport = class {
|
|
2625
2736
|
name = "local";
|
|
2626
2737
|
capabilities = localCapabilities;
|
|
@@ -2664,7 +2775,7 @@ var LocalTransport = class {
|
|
|
2664
2775
|
topology.prefix
|
|
2665
2776
|
);
|
|
2666
2777
|
if (!this.queues.has(queueName)) {
|
|
2667
|
-
this.queues.set(queueName,
|
|
2778
|
+
this.queues.set(queueName, /* @__PURE__ */ new Map());
|
|
2668
2779
|
}
|
|
2669
2780
|
}
|
|
2670
2781
|
}
|
|
@@ -2696,6 +2807,18 @@ var LocalTransport = class {
|
|
|
2696
2807
|
this.delayedTimers.add(timer);
|
|
2697
2808
|
}
|
|
2698
2809
|
async enqueue(queue, envelope) {
|
|
2810
|
+
const subs = this.subscriptions.get(queue);
|
|
2811
|
+
if (!subs || subs.length === 0) {
|
|
2812
|
+
throw new LocalTransportNoActiveSubscriberError(queue);
|
|
2813
|
+
}
|
|
2814
|
+
await this.storeAndDeliver(queue, envelope);
|
|
2815
|
+
}
|
|
2816
|
+
/**
|
|
2817
|
+
* Stores a message and delivers it to any active subscribers, without
|
|
2818
|
+
* requiring one to be present. Dead-letter queues are meant to hold
|
|
2819
|
+
* messages for later manual inspection, so they don't need a live consumer.
|
|
2820
|
+
*/
|
|
2821
|
+
async storeAndDeliver(queue, envelope) {
|
|
2699
2822
|
const messages = this.getOrCreateQueue(queue);
|
|
2700
2823
|
const messageId = `${++this.messageIdCounter}`;
|
|
2701
2824
|
const queuedMessage = {
|
|
@@ -2703,17 +2826,11 @@ var LocalTransport = class {
|
|
|
2703
2826
|
id: messageId,
|
|
2704
2827
|
completed: false
|
|
2705
2828
|
};
|
|
2706
|
-
messages.
|
|
2829
|
+
messages.set(messageId, queuedMessage);
|
|
2707
2830
|
await this.deliverToSubscribers(queue, queuedMessage);
|
|
2708
2831
|
}
|
|
2709
2832
|
async deliverToSubscribers(queue, message) {
|
|
2710
|
-
const subs = this.subscriptions.get(queue);
|
|
2711
|
-
if (!subs) {
|
|
2712
|
-
this.logger.warn(
|
|
2713
|
-
`[Matador][LocalTransport] \u{1F7E1} No subscriptions found for queue '${queue}', message will be lost.`
|
|
2714
|
-
);
|
|
2715
|
-
return;
|
|
2716
|
-
}
|
|
2833
|
+
const subs = this.subscriptions.get(queue) ?? [];
|
|
2717
2834
|
for (const sub of subs) {
|
|
2718
2835
|
if (!sub.active || message.completed) continue;
|
|
2719
2836
|
const receipt = {
|
|
@@ -2746,7 +2863,7 @@ var LocalTransport = class {
|
|
|
2746
2863
|
const subs = this.subscriptions.get(queue) ?? [];
|
|
2747
2864
|
subs.push(subscription);
|
|
2748
2865
|
this.subscriptions.set(queue, subs);
|
|
2749
|
-
const messages = this.queues.get(queue) ?? [];
|
|
2866
|
+
const messages = this.queues.get(queue)?.values() ?? [];
|
|
2750
2867
|
for (const message of messages) {
|
|
2751
2868
|
if (message.completed) continue;
|
|
2752
2869
|
await this.deliverToSubscribers(queue, message);
|
|
@@ -2761,6 +2878,11 @@ var LocalTransport = class {
|
|
|
2761
2878
|
this.subscriptions.delete(queue);
|
|
2762
2879
|
}
|
|
2763
2880
|
},
|
|
2881
|
+
// No-op: LocalTransport is only ever fed from within the process
|
|
2882
|
+
// So, when shutting down, it needs to continue and handle new messages until the end
|
|
2883
|
+
// As, an ongoing message (before shutdown) could create new (local) messages
|
|
2884
|
+
pauseForShutdown: async () => {
|
|
2885
|
+
},
|
|
2764
2886
|
get isActive() {
|
|
2765
2887
|
return subscription.active;
|
|
2766
2888
|
}
|
|
@@ -2770,10 +2892,14 @@ var LocalTransport = class {
|
|
|
2770
2892
|
const message = receipt.handle;
|
|
2771
2893
|
message.completed = true;
|
|
2772
2894
|
this.completedMessages.push(receipt);
|
|
2895
|
+
if (this.completedMessages.length > MAX_COMPLETED_MESSAGES) {
|
|
2896
|
+
this.completedMessages.shift();
|
|
2897
|
+
}
|
|
2898
|
+
this.queues.get(receipt.sourceQueue)?.delete(message.id);
|
|
2773
2899
|
}
|
|
2774
2900
|
async sendToDeadLetter(receipt, dlqName, envelope, _reason) {
|
|
2775
2901
|
const dlqQueueName = `${receipt.sourceQueue}.${dlqName}`;
|
|
2776
|
-
await this.
|
|
2902
|
+
await this.storeAndDeliver(dlqQueueName, envelope);
|
|
2777
2903
|
await this.complete(receipt);
|
|
2778
2904
|
}
|
|
2779
2905
|
// Test helpers
|
|
@@ -2781,9 +2907,7 @@ var LocalTransport = class {
|
|
|
2781
2907
|
* Gets the current size of a queue.
|
|
2782
2908
|
*/
|
|
2783
2909
|
getQueueSize(queue) {
|
|
2784
|
-
|
|
2785
|
-
if (!messages) return 0;
|
|
2786
|
-
return messages.filter((m) => !m.completed).length;
|
|
2910
|
+
return this.queues.get(queue)?.size ?? 0;
|
|
2787
2911
|
}
|
|
2788
2912
|
/**
|
|
2789
2913
|
* Gets all completed message receipts.
|
|
@@ -2797,7 +2921,7 @@ var LocalTransport = class {
|
|
|
2797
2921
|
getPendingMessages(queue) {
|
|
2798
2922
|
const messages = this.queues.get(queue);
|
|
2799
2923
|
if (!messages) return [];
|
|
2800
|
-
return messages.
|
|
2924
|
+
return Array.from(messages.values()).map((m) => m.envelope);
|
|
2801
2925
|
}
|
|
2802
2926
|
/**
|
|
2803
2927
|
* Clears all state (for test isolation).
|
|
@@ -2819,7 +2943,7 @@ var LocalTransport = class {
|
|
|
2819
2943
|
async receiveOne(queue) {
|
|
2820
2944
|
const messages = this.queues.get(queue);
|
|
2821
2945
|
if (!messages) return null;
|
|
2822
|
-
const pending = messages.
|
|
2946
|
+
const pending = messages.values().next().value;
|
|
2823
2947
|
if (!pending) return null;
|
|
2824
2948
|
const receipt = {
|
|
2825
2949
|
handle: pending,
|
|
@@ -2834,7 +2958,7 @@ var LocalTransport = class {
|
|
|
2834
2958
|
getOrCreateQueue(queue) {
|
|
2835
2959
|
let messages = this.queues.get(queue);
|
|
2836
2960
|
if (!messages) {
|
|
2837
|
-
messages =
|
|
2961
|
+
messages = /* @__PURE__ */ new Map();
|
|
2838
2962
|
this.queues.set(queue, messages);
|
|
2839
2963
|
}
|
|
2840
2964
|
return messages;
|
|
@@ -2977,6 +3101,11 @@ var MultiTransport = class {
|
|
|
2977
3101
|
unsubscribe: async () => {
|
|
2978
3102
|
await Promise.all(subscriptions.map((s) => s.unsubscribe()));
|
|
2979
3103
|
},
|
|
3104
|
+
pauseForShutdown: async () => {
|
|
3105
|
+
await Promise.all(
|
|
3106
|
+
subscriptions.map((s) => (s.pauseForShutdown ?? s.unsubscribe)())
|
|
3107
|
+
);
|
|
3108
|
+
},
|
|
2980
3109
|
get isActive() {
|
|
2981
3110
|
return subscriptions.some((s) => s.isActive);
|
|
2982
3111
|
}
|
|
@@ -3153,33 +3282,37 @@ var RabbitMQTransport = class {
|
|
|
3153
3282
|
};
|
|
3154
3283
|
this.subscriptionIntents.push(intent);
|
|
3155
3284
|
await this.activateIntent(intent);
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3285
|
+
const unsubscribe = async () => {
|
|
3286
|
+
intent.active = false;
|
|
3287
|
+
const idx = this.subscriptionIntents.indexOf(intent);
|
|
3288
|
+
if (idx !== -1) this.subscriptionIntents.splice(idx, 1);
|
|
3289
|
+
const consumer = intent.currentConsumer;
|
|
3290
|
+
if (consumer) {
|
|
3291
|
+
consumer.active = false;
|
|
3292
|
+
const queueChannel = this.queueChannels.get(queue);
|
|
3293
|
+
if (queueChannel) {
|
|
3294
|
+
try {
|
|
3295
|
+
await queueChannel.channel.cancel(consumer.consumerTag);
|
|
3296
|
+
} catch {
|
|
3297
|
+
}
|
|
3298
|
+
const cIdx = queueChannel.consumers.indexOf(consumer);
|
|
3299
|
+
if (cIdx !== -1) queueChannel.consumers.splice(cIdx, 1);
|
|
3300
|
+
if (queueChannel.consumers.length === 0) {
|
|
3166
3301
|
try {
|
|
3167
|
-
await queueChannel.channel.
|
|
3302
|
+
await queueChannel.channel.close();
|
|
3168
3303
|
} catch {
|
|
3169
3304
|
}
|
|
3170
|
-
|
|
3171
|
-
if (cIdx !== -1) queueChannel.consumers.splice(cIdx, 1);
|
|
3172
|
-
if (queueChannel.consumers.length === 0) {
|
|
3173
|
-
try {
|
|
3174
|
-
await queueChannel.channel.close();
|
|
3175
|
-
} catch {
|
|
3176
|
-
}
|
|
3177
|
-
this.queueChannels.delete(queue);
|
|
3178
|
-
}
|
|
3305
|
+
this.queueChannels.delete(queue);
|
|
3179
3306
|
}
|
|
3180
|
-
intent.currentConsumer = null;
|
|
3181
3307
|
}
|
|
3182
|
-
|
|
3308
|
+
intent.currentConsumer = null;
|
|
3309
|
+
}
|
|
3310
|
+
};
|
|
3311
|
+
return {
|
|
3312
|
+
unsubscribe,
|
|
3313
|
+
// Messages on this transport comes from outside (RabbitMQ), so, when shutting down, we need to unsubscribe directly
|
|
3314
|
+
// Otherwise, we could receive traffic forever, and an idle state would never be reached.
|
|
3315
|
+
pauseForShutdown: unsubscribe,
|
|
3183
3316
|
get isActive() {
|
|
3184
3317
|
return intent.active;
|
|
3185
3318
|
}
|
|
@@ -3634,7 +3767,7 @@ exports.InvalidEventError = InvalidEventError;
|
|
|
3634
3767
|
exports.InvalidSchemaError = InvalidSchemaError;
|
|
3635
3768
|
exports.JsonCodec = JsonCodec;
|
|
3636
3769
|
exports.LocalTransport = LocalTransport;
|
|
3637
|
-
exports.
|
|
3770
|
+
exports.LocalTransportNoActiveSubscriberError = LocalTransportNoActiveSubscriberError;
|
|
3638
3771
|
exports.Matador = Matador;
|
|
3639
3772
|
exports.MatadorError = MatadorError;
|
|
3640
3773
|
exports.MatadorEvent = MatadorEvent;
|