@zdavison/matador 4.0.1 → 4.0.2
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 +11 -0
- package/dist/core/fanout.d.ts.map +1 -1
- package/dist/core/fanout.js +103 -14
- package/dist/core/fanout.test.js +377 -5
- package/dist/core/matador.d.ts.map +1 -1
- package/dist/core/matador.js +1 -0
- package/dist/core/matador.test.js +1 -1
- package/dist/index.cjs +159 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/transport/multi/multi-transport.d.ts +6 -0
- package/dist/transport/multi/multi-transport.d.ts.map +1 -1
- package/dist/transport/multi/multi-transport.js +17 -0
- package/dist/transport/rabbitmq/rabbitmq-transport-reconnection.test.js +45 -6
- package/dist/transport/rabbitmq/rabbitmq-transport.d.ts +6 -0
- package/dist/transport/rabbitmq/rabbitmq-transport.d.ts.map +1 -1
- package/dist/transport/rabbitmq/rabbitmq-transport.js +52 -13
- package/dist/transport/transport.d.ts +6 -0
- package/dist/transport/transport.d.ts.map +1 -1
- package/dist/types/event.d.ts +17 -0
- package/dist/types/event.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -808,12 +808,22 @@ var FanoutEngine = class {
|
|
|
808
808
|
topology;
|
|
809
809
|
defaultQueue;
|
|
810
810
|
enqueuingCount = 0;
|
|
811
|
+
retryBuffer = [];
|
|
812
|
+
maxRetryBufferSize;
|
|
813
|
+
disposeOnConnected;
|
|
811
814
|
constructor(config) {
|
|
812
815
|
this.transport = config.transport;
|
|
813
816
|
this.schema = config.schema;
|
|
814
817
|
this.hooks = config.hooks;
|
|
815
818
|
this.topology = config.topology;
|
|
816
819
|
this.defaultQueue = config.defaultQueue;
|
|
820
|
+
this.maxRetryBufferSize = config.maxRetryBufferSize ?? 5e3;
|
|
821
|
+
this.disposeOnConnected = this.transport.onConnected?.(() => {
|
|
822
|
+
void this.flushRetryBuffer();
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
dispose() {
|
|
826
|
+
this.disposeOnConnected?.();
|
|
817
827
|
}
|
|
818
828
|
/**
|
|
819
829
|
* Current count of events being enqueued.
|
|
@@ -851,12 +861,13 @@ var FanoutEngine = class {
|
|
|
851
861
|
universalMetadata,
|
|
852
862
|
delayMs: options.delayMs
|
|
853
863
|
});
|
|
864
|
+
const sendOptions = options.delayMs !== void 0 ? { delay: options.delayMs } : void 0;
|
|
854
865
|
this.enqueuingCount++;
|
|
855
866
|
try {
|
|
856
867
|
const usedTransport = await this.transport.send(
|
|
857
868
|
qualifiedQueue,
|
|
858
869
|
envelope,
|
|
859
|
-
|
|
870
|
+
sendOptions
|
|
860
871
|
);
|
|
861
872
|
sent++;
|
|
862
873
|
await this.hooks.onEnqueueSuccess({
|
|
@@ -866,17 +877,48 @@ var FanoutEngine = class {
|
|
|
866
877
|
});
|
|
867
878
|
} catch (error) {
|
|
868
879
|
const cause = error instanceof Error ? error : new Error(String(error));
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
+
const shouldBuffer = options.buffer !== false;
|
|
881
|
+
if (shouldBuffer && this.retryBuffer.length < this.maxRetryBufferSize) {
|
|
882
|
+
this.retryBuffer.push({
|
|
883
|
+
queue: qualifiedQueue,
|
|
884
|
+
envelope,
|
|
885
|
+
sendOptions,
|
|
886
|
+
subscriberName: subscriber.name
|
|
887
|
+
});
|
|
888
|
+
this.hooks.logger.warn(
|
|
889
|
+
`[Matador] \u{1F7E1} Message for '${subscriber.name}' buffered for retry on reconnect (buffer: ${this.retryBuffer.length}/${this.maxRetryBufferSize}).`
|
|
890
|
+
);
|
|
891
|
+
if (options.throwOnBufferedFailure) {
|
|
892
|
+
const err = new TransportSendError(qualifiedQueue, cause);
|
|
893
|
+
errors.push({
|
|
894
|
+
subscriberName: subscriber.name,
|
|
895
|
+
queue: qualifiedQueue,
|
|
896
|
+
error: err
|
|
897
|
+
});
|
|
898
|
+
await this.hooks.onEnqueueError({
|
|
899
|
+
envelope,
|
|
900
|
+
error: err,
|
|
901
|
+
transport: this.transport.name
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
} else {
|
|
905
|
+
if (shouldBuffer) {
|
|
906
|
+
this.hooks.logger.error(
|
|
907
|
+
`[Matador] \u{1F534} Retry buffer full (${this.maxRetryBufferSize}). Message for '${subscriber.name}' dropped and will not be retried.`
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
const err = new TransportSendError(qualifiedQueue, cause);
|
|
911
|
+
errors.push({
|
|
912
|
+
subscriberName: subscriber.name,
|
|
913
|
+
queue: qualifiedQueue,
|
|
914
|
+
error: err
|
|
915
|
+
});
|
|
916
|
+
await this.hooks.onEnqueueError({
|
|
917
|
+
envelope,
|
|
918
|
+
error: err,
|
|
919
|
+
transport: this.transport.name
|
|
920
|
+
});
|
|
921
|
+
}
|
|
880
922
|
} finally {
|
|
881
923
|
this.enqueuingCount--;
|
|
882
924
|
}
|
|
@@ -888,6 +930,57 @@ var FanoutEngine = class {
|
|
|
888
930
|
errors
|
|
889
931
|
};
|
|
890
932
|
}
|
|
933
|
+
/**
|
|
934
|
+
* Flushes the retry buffer
|
|
935
|
+
*
|
|
936
|
+
* This is called when the transport reconnects, and is used to retry any messages that were buffered while the transport was disconnected
|
|
937
|
+
*/
|
|
938
|
+
async flushRetryBuffer() {
|
|
939
|
+
if (this.retryBuffer.length === 0) return;
|
|
940
|
+
this.hooks.logger.info(
|
|
941
|
+
`[Matador] \u23F3 Flushing ${this.retryBuffer.length} buffered message(s)...`
|
|
942
|
+
);
|
|
943
|
+
const toFlush = this.retryBuffer.splice(0);
|
|
944
|
+
for (const item of toFlush) {
|
|
945
|
+
this.enqueuingCount++;
|
|
946
|
+
try {
|
|
947
|
+
const usedTransport = await this.transport.send(
|
|
948
|
+
item.queue,
|
|
949
|
+
item.envelope,
|
|
950
|
+
item.sendOptions
|
|
951
|
+
);
|
|
952
|
+
await this.hooks.onEnqueueSuccess({
|
|
953
|
+
envelope: item.envelope,
|
|
954
|
+
queue: item.queue,
|
|
955
|
+
transport: usedTransport
|
|
956
|
+
});
|
|
957
|
+
} catch (error) {
|
|
958
|
+
if (this.retryBuffer.length < this.maxRetryBufferSize) {
|
|
959
|
+
this.retryBuffer.push(item);
|
|
960
|
+
} else {
|
|
961
|
+
const cause = error instanceof Error ? error : new Error(String(error));
|
|
962
|
+
const err = new TransportSendError(item.queue, cause);
|
|
963
|
+
await this.hooks.onEnqueueError({
|
|
964
|
+
envelope: item.envelope,
|
|
965
|
+
error: err,
|
|
966
|
+
transport: this.transport.name
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
} finally {
|
|
970
|
+
this.enqueuingCount--;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
const remaining = this.retryBuffer.length;
|
|
974
|
+
if (remaining > 0) {
|
|
975
|
+
this.hooks.logger.warn(
|
|
976
|
+
`[Matador] \u{1F7E1} ${remaining} buffered message(s) could not be flushed; will retry on next reconnect.`
|
|
977
|
+
);
|
|
978
|
+
} else {
|
|
979
|
+
this.hooks.logger.info(
|
|
980
|
+
"[Matador] \u{1F7E2} All buffered messages flushed successfully."
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
891
984
|
async isSubscriberEnabled(subscriber) {
|
|
892
985
|
if (!subscriber.enabled) {
|
|
893
986
|
return true;
|
|
@@ -2354,6 +2447,7 @@ var Matador = class {
|
|
|
2354
2447
|
if (!this.started) {
|
|
2355
2448
|
return;
|
|
2356
2449
|
}
|
|
2450
|
+
this.fanout.dispose();
|
|
2357
2451
|
await this.shutdownManager.shutdown();
|
|
2358
2452
|
this.started = false;
|
|
2359
2453
|
}
|
|
@@ -2792,6 +2886,19 @@ var MultiTransport = class {
|
|
|
2792
2886
|
isConnected() {
|
|
2793
2887
|
return this.connected && this.primary.isConnected();
|
|
2794
2888
|
}
|
|
2889
|
+
/**
|
|
2890
|
+
* Registers a callback to fire each time the transport successfully (re)connects
|
|
2891
|
+
* @param callback - The callback to fire when the transport successfully (re)connects
|
|
2892
|
+
* @returns A function to unsubscribe from the callback
|
|
2893
|
+
*/
|
|
2894
|
+
onConnected(callback) {
|
|
2895
|
+
const unsubFunctions = this.transports.map((t) => t.onConnected?.(callback)).filter(Boolean);
|
|
2896
|
+
return () => {
|
|
2897
|
+
for (const unsub of unsubFunctions) {
|
|
2898
|
+
unsub();
|
|
2899
|
+
}
|
|
2900
|
+
};
|
|
2901
|
+
}
|
|
2795
2902
|
async applyTopology(topology) {
|
|
2796
2903
|
await Promise.all(this.transports.map((t) => t.applyTopology(topology)));
|
|
2797
2904
|
}
|
|
@@ -2941,6 +3048,18 @@ var RabbitMQTransport = class {
|
|
|
2941
3048
|
isConnected() {
|
|
2942
3049
|
return this.connectionManager.isConnected();
|
|
2943
3050
|
}
|
|
3051
|
+
/**
|
|
3052
|
+
* Registers a callback to fire each time the transport (here RabbitMQ) successfully (re)connects
|
|
3053
|
+
* @param callback - The callback to fire when the transport (here RabbitMQ) successfully (re)connects
|
|
3054
|
+
* @returns A function to unsubscribe from the callback
|
|
3055
|
+
*/
|
|
3056
|
+
onConnected(callback) {
|
|
3057
|
+
return this.connectionManager.onStateChange((state) => {
|
|
3058
|
+
if (state.status === "connected") {
|
|
3059
|
+
callback();
|
|
3060
|
+
}
|
|
3061
|
+
});
|
|
3062
|
+
}
|
|
2944
3063
|
async applyTopology(topology) {
|
|
2945
3064
|
this.topology = topology;
|
|
2946
3065
|
if (!this.publishChannel) {
|
|
@@ -3219,6 +3338,14 @@ var RabbitMQTransport = class {
|
|
|
3219
3338
|
return queueChannel;
|
|
3220
3339
|
}
|
|
3221
3340
|
async doConnect() {
|
|
3341
|
+
if (this.connection) {
|
|
3342
|
+
try {
|
|
3343
|
+
await this.connection.close();
|
|
3344
|
+
} catch {
|
|
3345
|
+
}
|
|
3346
|
+
this.connection = null;
|
|
3347
|
+
this.publishChannel = null;
|
|
3348
|
+
}
|
|
3222
3349
|
this.queueChannels.clear();
|
|
3223
3350
|
this.logger.info(
|
|
3224
3351
|
`[Matador] \u23F3 Connecting to RabbitMQ at '${redactAmqpUrl(this.config.url)}'.`
|
|
@@ -3236,21 +3363,33 @@ var RabbitMQTransport = class {
|
|
|
3236
3363
|
consumer.active = false;
|
|
3237
3364
|
}
|
|
3238
3365
|
}
|
|
3366
|
+
this.connection = null;
|
|
3367
|
+
this.publishChannel = null;
|
|
3239
3368
|
if (this.connectionManager.isConnected()) {
|
|
3240
3369
|
this.connectionManager.handleConnectionLost(
|
|
3241
3370
|
new Error("Connection closed unexpectedly")
|
|
3242
3371
|
);
|
|
3243
3372
|
}
|
|
3244
3373
|
});
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
this.
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3374
|
+
try {
|
|
3375
|
+
this.publishChannel = await connection.createConfirmChannel();
|
|
3376
|
+
this.publishChannel.on("error", (err) => {
|
|
3377
|
+
this.logger.error("[Matador] \u{1F534} RabbitMQ publish channel error", err);
|
|
3378
|
+
});
|
|
3379
|
+
if (this.topology) {
|
|
3380
|
+
await this.applyTopology(this.topology);
|
|
3381
|
+
for (const intent of this.subscriptionIntents) {
|
|
3382
|
+
await this.activateIntent(intent);
|
|
3383
|
+
}
|
|
3253
3384
|
}
|
|
3385
|
+
} catch (err) {
|
|
3386
|
+
try {
|
|
3387
|
+
await connection.close();
|
|
3388
|
+
} catch {
|
|
3389
|
+
}
|
|
3390
|
+
this.connection = null;
|
|
3391
|
+
this.publishChannel = null;
|
|
3392
|
+
throw err;
|
|
3254
3393
|
}
|
|
3255
3394
|
this.logger.info("[Matador] \u{1F50C} Connected to RabbitMQ");
|
|
3256
3395
|
}
|