@relaybus/rabbitmq 0.3.0 → 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.cjs +241 -225
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -5
- package/dist/index.d.ts +12 -5
- package/dist/index.js +242 -227
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { RelayBusError, NonRetryableError, assertEventType,
|
|
2
|
-
import { createCloudEventReaders, mediaTypeOf, isJsonObject, createJsonEnvelopeFormat,
|
|
1
|
+
import { RelayBusError, NonRetryableError, assertEventType, ConfigurationError, systemClock, toErrorInfo, computeRetryDelay, isRetryableError, createRetryPolicy } from '@relaybus/core';
|
|
2
|
+
import { createCloudEventReaders, DecodedEventReader, mediaTypeOf, isJsonObject, observeRejection, createJsonEnvelopeFormat, SubscribingTransport, writeStructuredCloudEvent, schedule, NonEmptyText, PositiveInteger, Milliseconds } from '@relaybus/core/internal';
|
|
3
3
|
export { fromHeader, fromPayloadField } from '@relaybus/core/internal';
|
|
4
4
|
|
|
5
5
|
// src/errors.ts
|
|
@@ -31,6 +31,99 @@ var RabbitMqUnencodableEventError = class extends NonRetryableError {
|
|
|
31
31
|
name = "RabbitMqUnencodableEventError";
|
|
32
32
|
code = "RELAYBUS_RABBITMQ_UNENCODABLE_EVENT";
|
|
33
33
|
};
|
|
34
|
+
var NACK_MESSAGE = "message nacked";
|
|
35
|
+
var ConfirmedPublishing = class {
|
|
36
|
+
#transportName;
|
|
37
|
+
#publishTimeoutMs;
|
|
38
|
+
constructor(transportName, publishTimeoutMs) {
|
|
39
|
+
this.#transportName = transportName;
|
|
40
|
+
this.#publishTimeoutMs = publishTimeoutMs;
|
|
41
|
+
}
|
|
42
|
+
async publish(publisher, input) {
|
|
43
|
+
const { exchange, routingKey, content, options, mandatory } = input;
|
|
44
|
+
const eventId = options.messageId ?? "unknown";
|
|
45
|
+
if (publisher === void 0) {
|
|
46
|
+
throw new RabbitMqUnavailableError(
|
|
47
|
+
`[RabbitMqTransport](publish) Not connected to the broker | transport=${this.#transportName} destination=${routingKey}`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
const returnKey = unroutableKey(options.messageId, routingKey);
|
|
51
|
+
const target = { eventId, destination: routingKey };
|
|
52
|
+
await new Promise((resolve, reject) => {
|
|
53
|
+
const cancelTimeout = this.#startTimeout(target, reject);
|
|
54
|
+
const pending = { ...target, publisher, returnKey, cancelTimeout, resolve, reject };
|
|
55
|
+
try {
|
|
56
|
+
publisher.channel.publish(
|
|
57
|
+
exchange,
|
|
58
|
+
routingKey,
|
|
59
|
+
content,
|
|
60
|
+
{ ...options, mandatory },
|
|
61
|
+
(error) => this.#confirmed(pending, error)
|
|
62
|
+
);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
cancelTimeout();
|
|
65
|
+
reject(this.#toPublishError(error, target));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
#startTimeout({ eventId, destination }, reject) {
|
|
70
|
+
return schedule(systemClock, {
|
|
71
|
+
run: () => {
|
|
72
|
+
reject(
|
|
73
|
+
new RabbitMqUnavailableError(
|
|
74
|
+
`[RabbitMqTransport](publish) No broker confirm within ${this.#publishTimeoutMs}ms | transport=${this.#transportName} eventId=${eventId} destination=${destination}`
|
|
75
|
+
)
|
|
76
|
+
);
|
|
77
|
+
},
|
|
78
|
+
delayMs: this.#publishTimeoutMs
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/** The broker's confirm (or nack) of one message arrived. */
|
|
82
|
+
#confirmed(pending, error) {
|
|
83
|
+
pending.cancelTimeout();
|
|
84
|
+
const returned = takeUnroutable(pending.publisher.unroutable, pending.returnKey);
|
|
85
|
+
if (error) {
|
|
86
|
+
pending.reject(this.#toPublishError(error, pending));
|
|
87
|
+
} else if (returned) {
|
|
88
|
+
pending.reject(
|
|
89
|
+
new UnroutableMessageError(
|
|
90
|
+
`[RabbitMqTransport](publish) No queue is bound to the routing key | transport=${this.#transportName} eventId=${pending.eventId} destination=${pending.destination}`
|
|
91
|
+
)
|
|
92
|
+
);
|
|
93
|
+
} else {
|
|
94
|
+
pending.resolve();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
#toPublishError(error, { eventId, destination }) {
|
|
98
|
+
const { message } = toErrorInfo(error);
|
|
99
|
+
const detail = `transport=${this.#transportName} eventId=${eventId} destination=${destination} error=${message}`;
|
|
100
|
+
return message === NACK_MESSAGE ? new RabbitMqPublishRejectedError(
|
|
101
|
+
`[RabbitMqTransport](publish) Broker nacked the message | ${detail}`,
|
|
102
|
+
{ cause: error }
|
|
103
|
+
) : new RabbitMqUnavailableError(`[RabbitMqTransport](publish) Publish failed | ${detail}`, {
|
|
104
|
+
cause: error
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
function unroutableKey(messageId, routingKey) {
|
|
109
|
+
return `${String(messageId)}
|
|
110
|
+
${routingKey}`;
|
|
111
|
+
}
|
|
112
|
+
function recordUnroutable(unroutable, key) {
|
|
113
|
+
unroutable.set(key, (unroutable.get(key) ?? 0) + 1);
|
|
114
|
+
}
|
|
115
|
+
function takeUnroutable(unroutable, key) {
|
|
116
|
+
const count = unroutable.get(key);
|
|
117
|
+
if (count === void 0) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
if (count === 1) {
|
|
121
|
+
unroutable.delete(key);
|
|
122
|
+
} else {
|
|
123
|
+
unroutable.set(key, count - 1);
|
|
124
|
+
}
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
34
127
|
function abortableSleep(milliseconds, signal) {
|
|
35
128
|
if (signal.aborted) {
|
|
36
129
|
return Promise.resolve();
|
|
@@ -277,35 +370,41 @@ function visitObject(value, {
|
|
|
277
370
|
}
|
|
278
371
|
ancestors.add(value);
|
|
279
372
|
if (Array.isArray(value)) {
|
|
280
|
-
|
|
281
|
-
if (!(index in value)) {
|
|
282
|
-
throw refuse(eventId, {
|
|
283
|
-
path: `${path}[${index}]`,
|
|
284
|
-
reason: "is an array hole, which JSON turns into null"
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
visit(value[index], { path: `${path}[${index}]`, ancestors, eventId });
|
|
288
|
-
}
|
|
373
|
+
visitArray(value, { path, ancestors, eventId });
|
|
289
374
|
} else {
|
|
290
|
-
|
|
291
|
-
|
|
375
|
+
visitPlainObject(value, { path, ancestors, eventId });
|
|
376
|
+
}
|
|
377
|
+
ancestors.delete(value);
|
|
378
|
+
}
|
|
379
|
+
function visitArray(value, { path, ancestors, eventId }) {
|
|
380
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
381
|
+
if (!(index in value)) {
|
|
292
382
|
throw refuse(eventId, {
|
|
293
|
-
path
|
|
294
|
-
reason: "is
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
298
|
-
throw refuse(eventId, { path, reason: "has a symbol key, which JSON drops" });
|
|
299
|
-
}
|
|
300
|
-
for (const key of Object.keys(value)) {
|
|
301
|
-
visit(value[key], {
|
|
302
|
-
path: `${path}.${key}`,
|
|
303
|
-
ancestors,
|
|
304
|
-
eventId
|
|
383
|
+
path: `${path}[${index}]`,
|
|
384
|
+
reason: "is an array hole, which JSON turns into null"
|
|
305
385
|
});
|
|
306
386
|
}
|
|
387
|
+
visit(value[index], { path: `${path}[${index}]`, ancestors, eventId });
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function visitPlainObject(value, { path, ancestors, eventId }) {
|
|
391
|
+
const prototype = Object.getPrototypeOf(value);
|
|
392
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
393
|
+
throw refuse(eventId, {
|
|
394
|
+
path,
|
|
395
|
+
reason: "is not a plain object (class instance, Date, Map or Set)"
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
399
|
+
throw refuse(eventId, { path, reason: "has a symbol key, which JSON drops" });
|
|
400
|
+
}
|
|
401
|
+
for (const key of Object.keys(value)) {
|
|
402
|
+
visit(value[key], {
|
|
403
|
+
path: `${path}.${key}`,
|
|
404
|
+
ancestors,
|
|
405
|
+
eventId
|
|
406
|
+
});
|
|
307
407
|
}
|
|
308
|
-
ancestors.delete(value);
|
|
309
408
|
}
|
|
310
409
|
function refuse(eventId, { path, reason }) {
|
|
311
410
|
return new UnserializablePayloadError(
|
|
@@ -334,6 +433,22 @@ function encodeEnvelope(envelope) {
|
|
|
334
433
|
[`${ATTRIBUTE_PREFIX}time`]: envelope.occurredAt.toISOString(),
|
|
335
434
|
[`${ATTRIBUTE_PREFIX}datacontenttype`]: JSON_CONTENT_TYPE
|
|
336
435
|
};
|
|
436
|
+
addOptionalAttributes(headers, envelope);
|
|
437
|
+
return {
|
|
438
|
+
content: Buffer.from(JSON.stringify(envelope.payload), "utf8"),
|
|
439
|
+
options: {
|
|
440
|
+
contentType: JSON_CONTENT_TYPE,
|
|
441
|
+
persistent: true,
|
|
442
|
+
messageId: envelope.id,
|
|
443
|
+
timestamp: Math.floor(envelope.occurredAt.getTime() / MILLISECONDS_PER_SECOND),
|
|
444
|
+
...mirror("type", envelope.type),
|
|
445
|
+
...mirror("appId", envelope.source),
|
|
446
|
+
...envelope.correlationId === null ? {} : mirror("correlationId", envelope.correlationId),
|
|
447
|
+
headers
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
function addOptionalAttributes(headers, envelope) {
|
|
337
452
|
if (envelope.key !== null) {
|
|
338
453
|
headers[`${ATTRIBUTE_PREFIX}subject`] = envelope.key;
|
|
339
454
|
}
|
|
@@ -350,19 +465,6 @@ function encodeEnvelope(envelope) {
|
|
|
350
465
|
);
|
|
351
466
|
headers[`${EVENT_HEADER_PREFIX}${name}`] = value;
|
|
352
467
|
}
|
|
353
|
-
return {
|
|
354
|
-
content: Buffer.from(JSON.stringify(envelope.payload), "utf8"),
|
|
355
|
-
options: {
|
|
356
|
-
contentType: JSON_CONTENT_TYPE,
|
|
357
|
-
persistent: true,
|
|
358
|
-
messageId: envelope.id,
|
|
359
|
-
timestamp: Math.floor(envelope.occurredAt.getTime() / MILLISECONDS_PER_SECOND),
|
|
360
|
-
...mirror("type", envelope.type),
|
|
361
|
-
...mirror("appId", envelope.source),
|
|
362
|
-
...envelope.correlationId === null ? {} : mirror("correlationId", envelope.correlationId),
|
|
363
|
-
headers
|
|
364
|
-
}
|
|
365
|
-
};
|
|
366
468
|
}
|
|
367
469
|
function encodeStructuredEnvelope(envelope) {
|
|
368
470
|
assertJsonPayload(envelope.payload, envelope.id);
|
|
@@ -547,18 +649,13 @@ function plainJsonDecoder(options) {
|
|
|
547
649
|
if (properties.headers?.[SPEC_VERSION_HEADER] !== void 0 || mediaTypeOf(properties.contentType) === STRUCTURED_MEDIA_TYPE) {
|
|
548
650
|
return decodeCloudEvent(message);
|
|
549
651
|
}
|
|
550
|
-
const plain =
|
|
551
|
-
|
|
552
|
-
properties,
|
|
553
|
-
headers: readForeignHeaders(properties),
|
|
554
|
-
payload: parseJsonBody(message.content)
|
|
555
|
-
};
|
|
556
|
-
const type = resolveText(options.type, { message: plain, name: "type" });
|
|
652
|
+
const plain = plainMessageOf(message);
|
|
653
|
+
const type = RABBITMQ_EVENTS.text(options.type, { plain, name: "type" });
|
|
557
654
|
if (type === void 0) {
|
|
558
655
|
throw new MalformedMessageError("Message has no event type: the type option found none");
|
|
559
656
|
}
|
|
560
657
|
assertValidType(type, "The event type");
|
|
561
|
-
const id =
|
|
658
|
+
const id = RABBITMQ_EVENTS.text(options.id, { plain, name: "id" }) ?? nonEmptyText(properties.messageId);
|
|
562
659
|
if (id === void 0) {
|
|
563
660
|
throw new MalformedMessageError(
|
|
564
661
|
"Message has no id: set the messageId property on the producer or configure the id option"
|
|
@@ -567,8 +664,8 @@ function plainJsonDecoder(options) {
|
|
|
567
664
|
return {
|
|
568
665
|
id,
|
|
569
666
|
type,
|
|
570
|
-
source:
|
|
571
|
-
key:
|
|
667
|
+
source: RABBITMQ_EVENTS.text(options.source, { plain, name: "source" }) ?? defaultSource(message),
|
|
668
|
+
key: RABBITMQ_EVENTS.text(options.key, { plain, name: "key" }) ?? null,
|
|
572
669
|
payload: plain.payload,
|
|
573
670
|
headers: plain.headers,
|
|
574
671
|
correlationId: null,
|
|
@@ -577,27 +674,35 @@ function plainJsonDecoder(options) {
|
|
|
577
674
|
};
|
|
578
675
|
};
|
|
579
676
|
}
|
|
580
|
-
function
|
|
581
|
-
|
|
582
|
-
throw new MalformedMessageError("The decoder must return an event object synchronously");
|
|
583
|
-
}
|
|
584
|
-
if ("then" in decoded) {
|
|
585
|
-
observeRejection(decoded);
|
|
586
|
-
throw new MalformedMessageError("The decoder must return an event object synchronously");
|
|
587
|
-
}
|
|
588
|
-
const event = decoded;
|
|
677
|
+
function plainMessageOf(message) {
|
|
678
|
+
const { properties } = message;
|
|
589
679
|
return {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
payload: event.payload,
|
|
595
|
-
headers: event.headers ?? {},
|
|
596
|
-
correlationId: event.correlationId ?? null,
|
|
597
|
-
causationId: event.causationId ?? null,
|
|
598
|
-
occurredAt: event.occurredAt ?? propertyTimeOrNow(message.properties)
|
|
680
|
+
routingKey: message.fields.routingKey,
|
|
681
|
+
properties,
|
|
682
|
+
headers: readForeignHeaders(properties),
|
|
683
|
+
payload: parseJsonBody(message.content)
|
|
599
684
|
};
|
|
600
685
|
}
|
|
686
|
+
var RabbitMqDecodedEvents = class extends DecodedEventReader {
|
|
687
|
+
invalid(reason) {
|
|
688
|
+
return new MalformedMessageError(reason);
|
|
689
|
+
}
|
|
690
|
+
defaultSource(message) {
|
|
691
|
+
return defaultSource(message);
|
|
692
|
+
}
|
|
693
|
+
/** None: AMQP messages carry no ordering key. */
|
|
694
|
+
defaultKey() {
|
|
695
|
+
return null;
|
|
696
|
+
}
|
|
697
|
+
/** The `timestamp` property, else the time of receipt. */
|
|
698
|
+
defaultTime(message) {
|
|
699
|
+
return propertyTimeOrNow(message.properties);
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
var RABBITMQ_EVENTS = new RabbitMqDecodedEvents();
|
|
703
|
+
function completeDecodedEvent(decoded, message) {
|
|
704
|
+
return RABBITMQ_EVENTS.complete(decoded, message);
|
|
705
|
+
}
|
|
601
706
|
function parseJsonBody(content) {
|
|
602
707
|
let text2;
|
|
603
708
|
try {
|
|
@@ -642,19 +747,6 @@ function readForeignHeaders(properties) {
|
|
|
642
747
|
)
|
|
643
748
|
);
|
|
644
749
|
}
|
|
645
|
-
function resolveText(option, { message, name }) {
|
|
646
|
-
const value = typeof option === "function" ? option(message) : option;
|
|
647
|
-
if (value === void 0 || value === null) {
|
|
648
|
-
return void 0;
|
|
649
|
-
}
|
|
650
|
-
if (typeof value !== "string") {
|
|
651
|
-
observeRejection(value);
|
|
652
|
-
throw new MalformedMessageError(
|
|
653
|
-
`The ${name} option returned ${typeof value}, expected a string`
|
|
654
|
-
);
|
|
655
|
-
}
|
|
656
|
-
return value.length === 0 ? void 0 : value;
|
|
657
|
-
}
|
|
658
750
|
function resolveOccurredAt(option, message) {
|
|
659
751
|
const value = option?.(message);
|
|
660
752
|
if (value === void 0) {
|
|
@@ -666,11 +758,6 @@ function resolveOccurredAt(option, message) {
|
|
|
666
758
|
}
|
|
667
759
|
return value;
|
|
668
760
|
}
|
|
669
|
-
function observeRejection(value) {
|
|
670
|
-
if (typeof value === "object" && value !== null && "then" in value) {
|
|
671
|
-
Promise.resolve(value).catch(() => void 0);
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
761
|
|
|
675
762
|
// src/codecs.ts
|
|
676
763
|
var cloudEventsBinary = Object.freeze({
|
|
@@ -722,6 +809,12 @@ function requireMessageId(message) {
|
|
|
722
809
|
}
|
|
723
810
|
return id;
|
|
724
811
|
}
|
|
812
|
+
function decodeRabbitMqMessage(codec, message) {
|
|
813
|
+
if (codec === cloudEventsBinary) {
|
|
814
|
+
return decodeEnvelope(message);
|
|
815
|
+
}
|
|
816
|
+
return completeDecodedEvent(codec.decode(message), message);
|
|
817
|
+
}
|
|
725
818
|
var REDELIVERIES_HEADER = "x-relaybus-redeliveries";
|
|
726
819
|
var FIRST_FAILURE_HEADER = "x-relaybus-first-failure-at";
|
|
727
820
|
var DEAD_LETTER_REASON_HEADER = "x-relaybus-dead-letter-reason";
|
|
@@ -841,7 +934,7 @@ function resolveSettings(options) {
|
|
|
841
934
|
};
|
|
842
935
|
}
|
|
843
936
|
function resolveBasics(options) {
|
|
844
|
-
const exchange =
|
|
937
|
+
const exchange = NonEmptyText.of(options.exchange ?? "relaybus", "exchange");
|
|
845
938
|
const failOnConnectError = options.failOnConnectError ?? true;
|
|
846
939
|
if (!failOnConnectError && options.reconnect === false) {
|
|
847
940
|
throw new ConfigurationError(
|
|
@@ -849,7 +942,7 @@ function resolveBasics(options) {
|
|
|
849
942
|
);
|
|
850
943
|
}
|
|
851
944
|
const ordered = options.ordered ?? false;
|
|
852
|
-
const prefetch =
|
|
945
|
+
const prefetch = PositiveInteger.of(
|
|
853
946
|
options.prefetch ?? (ordered ? 1 : DEFAULT_PREFETCH),
|
|
854
947
|
"prefetch"
|
|
855
948
|
);
|
|
@@ -861,19 +954,19 @@ function resolveBasics(options) {
|
|
|
861
954
|
}
|
|
862
955
|
function resolveTopology(options, { exchange, prefetch }) {
|
|
863
956
|
return {
|
|
864
|
-
name:
|
|
957
|
+
name: NonEmptyText.of(options.name ?? "rabbitmq", "name"),
|
|
865
958
|
connect: resolveConnect(options),
|
|
866
959
|
exchange,
|
|
867
960
|
prefetch,
|
|
868
|
-
queuePrefix:
|
|
869
|
-
consumerGroup:
|
|
870
|
-
queue:
|
|
871
|
-
deadLetterExchange:
|
|
961
|
+
queuePrefix: NonEmptyText.of(options.queuePrefix ?? "relaybus", "queuePrefix"),
|
|
962
|
+
consumerGroup: NonEmptyText.optional(options.consumerGroup, "consumerGroup"),
|
|
963
|
+
queue: NonEmptyText.optional(options.queue, "queue"),
|
|
964
|
+
deadLetterExchange: NonEmptyText.of(
|
|
872
965
|
options.deadLetterExchange ?? `${exchange}.dead`,
|
|
873
966
|
"deadLetterExchange"
|
|
874
967
|
),
|
|
875
|
-
deadLetterQueue:
|
|
876
|
-
deliveryLimit:
|
|
968
|
+
deadLetterQueue: NonEmptyText.optional(options.deadLetterQueue, "deadLetterQueue"),
|
|
969
|
+
deliveryLimit: PositiveInteger.of(
|
|
877
970
|
options.deliveryLimit ?? DEFAULT_DELIVERY_LIMIT,
|
|
878
971
|
"deliveryLimit"
|
|
879
972
|
)
|
|
@@ -884,7 +977,7 @@ function resolveRetries(options, { ordered, redelivery }) {
|
|
|
884
977
|
handlerRetry: createRetryPolicy(options.handlerRetry, DEFAULT_HANDLER_RETRY),
|
|
885
978
|
redelivery,
|
|
886
979
|
retryLadder: ordered ? [] : buildRetryLadder(redelivery),
|
|
887
|
-
redeliveryMaxAgeMs:
|
|
980
|
+
redeliveryMaxAgeMs: Milliseconds.positive(
|
|
888
981
|
options.redelivery?.maxAge ?? DEFAULT_REDELIVERY_MAX_AGE,
|
|
889
982
|
"redelivery.maxAge"
|
|
890
983
|
),
|
|
@@ -896,8 +989,8 @@ function resolveRuntime(options, { failOnConnectError }) {
|
|
|
896
989
|
reconnect: resolveReconnect(options.reconnect),
|
|
897
990
|
failOnConnectError,
|
|
898
991
|
allowUnroutable: options.allowUnroutable ?? false,
|
|
899
|
-
publishTimeoutMs:
|
|
900
|
-
shutdownTimeoutMs:
|
|
992
|
+
publishTimeoutMs: Milliseconds.positive(options.publishTimeout ?? "30s", "publishTimeout"),
|
|
993
|
+
shutdownTimeoutMs: Milliseconds.of(options.shutdownTimeout ?? "30s", "shutdownTimeout"),
|
|
901
994
|
...resolveCodec(options),
|
|
902
995
|
logger: options.logger ?? console,
|
|
903
996
|
random: options.random ?? Math.random
|
|
@@ -927,7 +1020,7 @@ function resolveCodec({
|
|
|
927
1020
|
}
|
|
928
1021
|
return {
|
|
929
1022
|
encode: chosen.encode,
|
|
930
|
-
decode: (message) =>
|
|
1023
|
+
decode: (message) => decodeRabbitMqMessage(chosen, message)
|
|
931
1024
|
};
|
|
932
1025
|
}
|
|
933
1026
|
function resolveConnect(options) {
|
|
@@ -950,22 +1043,6 @@ function resolveReconnect(reconnect) {
|
|
|
950
1043
|
}
|
|
951
1044
|
return createRetryPolicy({ ...reconnect, attempts: 1 }, DEFAULT_RECONNECT);
|
|
952
1045
|
}
|
|
953
|
-
function nonEmpty(value, name) {
|
|
954
|
-
if (value.length === 0) {
|
|
955
|
-
throw new ConfigurationError(`${name} must not be empty`);
|
|
956
|
-
}
|
|
957
|
-
return value;
|
|
958
|
-
}
|
|
959
|
-
function optionalNonEmpty(value, name) {
|
|
960
|
-
return value === void 0 ? void 0 : nonEmpty(value, name);
|
|
961
|
-
}
|
|
962
|
-
function positiveDuration(value, name) {
|
|
963
|
-
const milliseconds = toMilliseconds(value, name);
|
|
964
|
-
if (milliseconds === 0) {
|
|
965
|
-
throw new ConfigurationError(`${name} must be greater than zero`);
|
|
966
|
-
}
|
|
967
|
-
return milliseconds;
|
|
968
|
-
}
|
|
969
1046
|
|
|
970
1047
|
// src/topology.ts
|
|
971
1048
|
function retryQueueName(queue, delayMs) {
|
|
@@ -1308,20 +1385,22 @@ var SerialQueue = class {
|
|
|
1308
1385
|
};
|
|
1309
1386
|
|
|
1310
1387
|
// src/rabbitmq-transport.ts
|
|
1311
|
-
var
|
|
1312
|
-
var RabbitMqTransport = class {
|
|
1388
|
+
var RabbitMqTransport = class extends SubscribingTransport {
|
|
1313
1389
|
name;
|
|
1314
1390
|
system = "rabbitmq";
|
|
1315
1391
|
#settings;
|
|
1316
1392
|
#supervisor;
|
|
1393
|
+
#publishing;
|
|
1317
1394
|
/** Serializes topology work so a subscription never races a reconnect. */
|
|
1318
1395
|
#topologyWork = new SerialQueue();
|
|
1319
1396
|
#live;
|
|
1320
1397
|
#subscription;
|
|
1321
1398
|
#closing;
|
|
1322
1399
|
constructor(options) {
|
|
1400
|
+
super();
|
|
1323
1401
|
this.#settings = resolveSettings(options);
|
|
1324
1402
|
this.name = this.#settings.name;
|
|
1403
|
+
this.#publishing = new ConfirmedPublishing(this.name, this.#settings.publishTimeoutMs);
|
|
1325
1404
|
this.#supervisor = new ConnectionSupervisor({
|
|
1326
1405
|
connect: this.#settings.connect,
|
|
1327
1406
|
reconnect: this.#settings.reconnect,
|
|
@@ -1363,7 +1442,7 @@ var RabbitMqTransport = class {
|
|
|
1363
1442
|
async publish(envelope, destination) {
|
|
1364
1443
|
assertShortString("Destination (routing key)", destination);
|
|
1365
1444
|
const { content, options } = this.#settings.encode(envelope);
|
|
1366
|
-
await this.#
|
|
1445
|
+
await this.#publishing.publish(this.#live?.publisher, {
|
|
1367
1446
|
exchange: this.#settings.exchange,
|
|
1368
1447
|
routingKey: destination,
|
|
1369
1448
|
content,
|
|
@@ -1371,63 +1450,16 @@ var RabbitMqTransport = class {
|
|
|
1371
1450
|
mandatory: !this.#settings.allowUnroutable
|
|
1372
1451
|
});
|
|
1373
1452
|
}
|
|
1374
|
-
async #publishConfirmed(input) {
|
|
1375
|
-
const { exchange, routingKey, content, options, mandatory } = input;
|
|
1376
|
-
const publisher = this.#live?.publisher;
|
|
1377
|
-
const eventId = options.messageId ?? "unknown";
|
|
1378
|
-
if (publisher === void 0) {
|
|
1379
|
-
throw new RabbitMqUnavailableError(
|
|
1380
|
-
`[RabbitMqTransport](publish) Not connected to the broker | transport=${this.name} destination=${routingKey}`
|
|
1381
|
-
);
|
|
1382
|
-
}
|
|
1383
|
-
const returnKey = unroutableKey(options.messageId, routingKey);
|
|
1384
|
-
await new Promise((resolve, reject) => {
|
|
1385
|
-
const cancelTimeout = schedule(systemClock, {
|
|
1386
|
-
run: () => {
|
|
1387
|
-
reject(
|
|
1388
|
-
new RabbitMqUnavailableError(
|
|
1389
|
-
`[RabbitMqTransport](publish) No broker confirm within ${this.#settings.publishTimeoutMs}ms | transport=${this.name} eventId=${eventId} destination=${routingKey}`
|
|
1390
|
-
)
|
|
1391
|
-
);
|
|
1392
|
-
},
|
|
1393
|
-
delayMs: this.#settings.publishTimeoutMs
|
|
1394
|
-
});
|
|
1395
|
-
const onConfirm = (error) => {
|
|
1396
|
-
cancelTimeout();
|
|
1397
|
-
const returned = takeUnroutable(publisher.unroutable, returnKey);
|
|
1398
|
-
if (error) {
|
|
1399
|
-
reject(this.#toPublishError(error, { eventId, destination: routingKey }));
|
|
1400
|
-
} else if (returned) {
|
|
1401
|
-
reject(
|
|
1402
|
-
new UnroutableMessageError(
|
|
1403
|
-
`[RabbitMqTransport](publish) No queue is bound to the routing key | transport=${this.name} eventId=${eventId} destination=${routingKey}`
|
|
1404
|
-
)
|
|
1405
|
-
);
|
|
1406
|
-
} else {
|
|
1407
|
-
resolve();
|
|
1408
|
-
}
|
|
1409
|
-
};
|
|
1410
|
-
try {
|
|
1411
|
-
publisher.channel.publish(
|
|
1412
|
-
exchange,
|
|
1413
|
-
routingKey,
|
|
1414
|
-
content,
|
|
1415
|
-
{ ...options, mandatory },
|
|
1416
|
-
onConfirm
|
|
1417
|
-
);
|
|
1418
|
-
} catch (error) {
|
|
1419
|
-
cancelTimeout();
|
|
1420
|
-
reject(this.#toPublishError(error, { eventId, destination: routingKey }));
|
|
1421
|
-
}
|
|
1422
|
-
});
|
|
1423
|
-
}
|
|
1424
1453
|
/**
|
|
1425
1454
|
* Binds one durable quorum queue to `destinations` and delivers its messages
|
|
1426
1455
|
* to `onMessage`. Call it once per transport; give each subscription its own
|
|
1427
1456
|
* transport instance, with its own `name` and `consumerGroup`.
|
|
1428
1457
|
*/
|
|
1429
|
-
|
|
1430
|
-
|
|
1458
|
+
async openSubscription({
|
|
1459
|
+
destinations,
|
|
1460
|
+
onMessage,
|
|
1461
|
+
hooks
|
|
1462
|
+
}) {
|
|
1431
1463
|
if (this.#subscription !== void 0) {
|
|
1432
1464
|
throw new ConfigurationError(
|
|
1433
1465
|
`Transport '${this.name}' already has a subscription; create another transport for another consumer group`
|
|
@@ -1438,34 +1470,8 @@ var RabbitMqTransport = class {
|
|
|
1438
1470
|
}
|
|
1439
1471
|
const queue = resolveQueueName(this.#settings);
|
|
1440
1472
|
const subscription = {
|
|
1441
|
-
topology:
|
|
1442
|
-
|
|
1443
|
-
queue,
|
|
1444
|
-
deadLetterExchange: this.#settings.deadLetterExchange,
|
|
1445
|
-
deadLetterQueue: this.#settings.deadLetterQueue ?? `${queue}.dead`,
|
|
1446
|
-
deliveryLimit: this.#settings.deliveryLimit,
|
|
1447
|
-
ordered: this.#settings.ordered,
|
|
1448
|
-
retryDelaysMs: this.#settings.retryLadder,
|
|
1449
|
-
destinations
|
|
1450
|
-
},
|
|
1451
|
-
consumer: new QueueConsumer({
|
|
1452
|
-
queue,
|
|
1453
|
-
prefetch: this.#settings.prefetch,
|
|
1454
|
-
handlerRetry: this.#settings.handlerRetry,
|
|
1455
|
-
redelivery: this.#settings.redelivery,
|
|
1456
|
-
retryLadder: this.#settings.retryLadder,
|
|
1457
|
-
redeliveryMaxAgeMs: this.#settings.redeliveryMaxAgeMs,
|
|
1458
|
-
ordered: this.#settings.ordered,
|
|
1459
|
-
deadLetterExchange: this.#settings.deadLetterExchange,
|
|
1460
|
-
republish: ({ exchange, routingKey }, { content, options }) => this.#publishConfirmed({ exchange, routingKey, content, options, mandatory: true }),
|
|
1461
|
-
shutdownTimeoutMs: this.#settings.shutdownTimeoutMs,
|
|
1462
|
-
onMessage,
|
|
1463
|
-
...hooks === void 0 ? {} : { onInvalidMessage: hooks.onInvalidMessage },
|
|
1464
|
-
decode: this.#settings.decode,
|
|
1465
|
-
onConsumerLost: (reason) => this.#supervisor.recover(reason),
|
|
1466
|
-
logger: this.#settings.logger,
|
|
1467
|
-
random: this.#settings.random
|
|
1468
|
-
})
|
|
1473
|
+
topology: this.#topologyOf(queue, destinations),
|
|
1474
|
+
consumer: this.#createConsumer(queue, { onMessage, hooks })
|
|
1469
1475
|
};
|
|
1470
1476
|
await this.#topologyWork.run(async () => {
|
|
1471
1477
|
this.#subscription = subscription;
|
|
@@ -1474,6 +1480,44 @@ var RabbitMqTransport = class {
|
|
|
1474
1480
|
}
|
|
1475
1481
|
});
|
|
1476
1482
|
}
|
|
1483
|
+
#topologyOf(queue, destinations) {
|
|
1484
|
+
return {
|
|
1485
|
+
exchange: this.#settings.exchange,
|
|
1486
|
+
queue,
|
|
1487
|
+
deadLetterExchange: this.#settings.deadLetterExchange,
|
|
1488
|
+
deadLetterQueue: this.#settings.deadLetterQueue ?? `${queue}.dead`,
|
|
1489
|
+
deliveryLimit: this.#settings.deliveryLimit,
|
|
1490
|
+
ordered: this.#settings.ordered,
|
|
1491
|
+
retryDelaysMs: this.#settings.retryLadder,
|
|
1492
|
+
destinations
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
#createConsumer(queue, { onMessage, hooks }) {
|
|
1496
|
+
return new QueueConsumer({
|
|
1497
|
+
queue,
|
|
1498
|
+
prefetch: this.#settings.prefetch,
|
|
1499
|
+
handlerRetry: this.#settings.handlerRetry,
|
|
1500
|
+
redelivery: this.#settings.redelivery,
|
|
1501
|
+
retryLadder: this.#settings.retryLadder,
|
|
1502
|
+
redeliveryMaxAgeMs: this.#settings.redeliveryMaxAgeMs,
|
|
1503
|
+
ordered: this.#settings.ordered,
|
|
1504
|
+
deadLetterExchange: this.#settings.deadLetterExchange,
|
|
1505
|
+
republish: ({ exchange, routingKey }, { content, options }) => this.#publishing.publish(this.#live?.publisher, {
|
|
1506
|
+
exchange,
|
|
1507
|
+
routingKey,
|
|
1508
|
+
content,
|
|
1509
|
+
options,
|
|
1510
|
+
mandatory: true
|
|
1511
|
+
}),
|
|
1512
|
+
shutdownTimeoutMs: this.#settings.shutdownTimeoutMs,
|
|
1513
|
+
onMessage,
|
|
1514
|
+
...hooks === void 0 ? {} : { onInvalidMessage: hooks.onInvalidMessage },
|
|
1515
|
+
decode: this.#settings.decode,
|
|
1516
|
+
onConsumerLost: (reason) => this.#supervisor.recover(reason),
|
|
1517
|
+
logger: this.#settings.logger,
|
|
1518
|
+
random: this.#settings.random
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1477
1521
|
/**
|
|
1478
1522
|
* Stops consuming, waits up to `shutdownTimeout` for running handlers
|
|
1479
1523
|
* (messages still being retried go back to the queue), then closes the
|
|
@@ -1543,37 +1587,8 @@ var RabbitMqTransport = class {
|
|
|
1543
1587
|
this.#live = void 0;
|
|
1544
1588
|
this.#subscription?.consumer.detach();
|
|
1545
1589
|
}
|
|
1546
|
-
#toPublishError(error, { eventId, destination }) {
|
|
1547
|
-
const { message } = toErrorInfo(error);
|
|
1548
|
-
const detail = `transport=${this.name} eventId=${eventId} destination=${destination} error=${message}`;
|
|
1549
|
-
return message === NACK_MESSAGE ? new RabbitMqPublishRejectedError(
|
|
1550
|
-
`[RabbitMqTransport](publish) Broker nacked the message | ${detail}`,
|
|
1551
|
-
{ cause: error }
|
|
1552
|
-
) : new RabbitMqUnavailableError(`[RabbitMqTransport](publish) Publish failed | ${detail}`, {
|
|
1553
|
-
cause: error
|
|
1554
|
-
});
|
|
1555
|
-
}
|
|
1556
1590
|
};
|
|
1557
|
-
function unroutableKey(messageId, routingKey) {
|
|
1558
|
-
return `${String(messageId)}
|
|
1559
|
-
${routingKey}`;
|
|
1560
|
-
}
|
|
1561
|
-
function recordUnroutable(unroutable, key) {
|
|
1562
|
-
unroutable.set(key, (unroutable.get(key) ?? 0) + 1);
|
|
1563
|
-
}
|
|
1564
|
-
function takeUnroutable(unroutable, key) {
|
|
1565
|
-
const count = unroutable.get(key);
|
|
1566
|
-
if (count === void 0) {
|
|
1567
|
-
return false;
|
|
1568
|
-
}
|
|
1569
|
-
if (count === 1) {
|
|
1570
|
-
unroutable.delete(key);
|
|
1571
|
-
} else {
|
|
1572
|
-
unroutable.set(key, count - 1);
|
|
1573
|
-
}
|
|
1574
|
-
return true;
|
|
1575
|
-
}
|
|
1576
1591
|
|
|
1577
|
-
export { AmqpLimitExceededError, MalformedMessageError, RabbitMqPublishRejectedError, RabbitMqTransport, RabbitMqUnavailableError, RabbitMqUnencodableEventError, UnroutableMessageError, UnserializablePayloadError, cloudEventsBinary, cloudEventsStructured, decodeCloudEvent, jsonEnvelope, plainJsonDecoder };
|
|
1592
|
+
export { AmqpLimitExceededError, MalformedMessageError, RabbitMqPublishRejectedError, RabbitMqTransport, RabbitMqUnavailableError, RabbitMqUnencodableEventError, UnroutableMessageError, UnserializablePayloadError, cloudEventsBinary, cloudEventsStructured, decodeCloudEvent, decodeRabbitMqMessage, jsonEnvelope, plainJsonDecoder };
|
|
1578
1593
|
//# sourceMappingURL=index.js.map
|
|
1579
1594
|
//# sourceMappingURL=index.js.map
|