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