@relaybus/rabbitmq 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +233 -224
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +234 -225
- 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({
|
|
@@ -842,7 +929,7 @@ function resolveSettings(options) {
|
|
|
842
929
|
};
|
|
843
930
|
}
|
|
844
931
|
function resolveBasics(options) {
|
|
845
|
-
const exchange =
|
|
932
|
+
const exchange = internal.NonEmptyText.of(options.exchange ?? "relaybus", "exchange");
|
|
846
933
|
const failOnConnectError = options.failOnConnectError ?? true;
|
|
847
934
|
if (!failOnConnectError && options.reconnect === false) {
|
|
848
935
|
throw new core.ConfigurationError(
|
|
@@ -850,7 +937,7 @@ function resolveBasics(options) {
|
|
|
850
937
|
);
|
|
851
938
|
}
|
|
852
939
|
const ordered = options.ordered ?? false;
|
|
853
|
-
const prefetch = internal.
|
|
940
|
+
const prefetch = internal.PositiveInteger.of(
|
|
854
941
|
options.prefetch ?? (ordered ? 1 : DEFAULT_PREFETCH),
|
|
855
942
|
"prefetch"
|
|
856
943
|
);
|
|
@@ -862,19 +949,19 @@ function resolveBasics(options) {
|
|
|
862
949
|
}
|
|
863
950
|
function resolveTopology(options, { exchange, prefetch }) {
|
|
864
951
|
return {
|
|
865
|
-
name:
|
|
952
|
+
name: internal.NonEmptyText.of(options.name ?? "rabbitmq", "name"),
|
|
866
953
|
connect: resolveConnect(options),
|
|
867
954
|
exchange,
|
|
868
955
|
prefetch,
|
|
869
|
-
queuePrefix:
|
|
870
|
-
consumerGroup:
|
|
871
|
-
queue:
|
|
872
|
-
deadLetterExchange:
|
|
956
|
+
queuePrefix: internal.NonEmptyText.of(options.queuePrefix ?? "relaybus", "queuePrefix"),
|
|
957
|
+
consumerGroup: internal.NonEmptyText.optional(options.consumerGroup, "consumerGroup"),
|
|
958
|
+
queue: internal.NonEmptyText.optional(options.queue, "queue"),
|
|
959
|
+
deadLetterExchange: internal.NonEmptyText.of(
|
|
873
960
|
options.deadLetterExchange ?? `${exchange}.dead`,
|
|
874
961
|
"deadLetterExchange"
|
|
875
962
|
),
|
|
876
|
-
deadLetterQueue:
|
|
877
|
-
deliveryLimit: internal.
|
|
963
|
+
deadLetterQueue: internal.NonEmptyText.optional(options.deadLetterQueue, "deadLetterQueue"),
|
|
964
|
+
deliveryLimit: internal.PositiveInteger.of(
|
|
878
965
|
options.deliveryLimit ?? DEFAULT_DELIVERY_LIMIT,
|
|
879
966
|
"deliveryLimit"
|
|
880
967
|
)
|
|
@@ -885,7 +972,7 @@ function resolveRetries(options, { ordered, redelivery }) {
|
|
|
885
972
|
handlerRetry: core.createRetryPolicy(options.handlerRetry, DEFAULT_HANDLER_RETRY),
|
|
886
973
|
redelivery,
|
|
887
974
|
retryLadder: ordered ? [] : buildRetryLadder(redelivery),
|
|
888
|
-
redeliveryMaxAgeMs:
|
|
975
|
+
redeliveryMaxAgeMs: internal.Milliseconds.positive(
|
|
889
976
|
options.redelivery?.maxAge ?? DEFAULT_REDELIVERY_MAX_AGE,
|
|
890
977
|
"redelivery.maxAge"
|
|
891
978
|
),
|
|
@@ -897,8 +984,8 @@ function resolveRuntime(options, { failOnConnectError }) {
|
|
|
897
984
|
reconnect: resolveReconnect(options.reconnect),
|
|
898
985
|
failOnConnectError,
|
|
899
986
|
allowUnroutable: options.allowUnroutable ?? false,
|
|
900
|
-
publishTimeoutMs:
|
|
901
|
-
shutdownTimeoutMs:
|
|
987
|
+
publishTimeoutMs: internal.Milliseconds.positive(options.publishTimeout ?? "30s", "publishTimeout"),
|
|
988
|
+
shutdownTimeoutMs: internal.Milliseconds.of(options.shutdownTimeout ?? "30s", "shutdownTimeout"),
|
|
902
989
|
...resolveCodec(options),
|
|
903
990
|
logger: options.logger ?? console,
|
|
904
991
|
random: options.random ?? Math.random
|
|
@@ -951,22 +1038,6 @@ function resolveReconnect(reconnect) {
|
|
|
951
1038
|
}
|
|
952
1039
|
return core.createRetryPolicy({ ...reconnect, attempts: 1 }, DEFAULT_RECONNECT);
|
|
953
1040
|
}
|
|
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
1041
|
|
|
971
1042
|
// src/topology.ts
|
|
972
1043
|
function retryQueueName(queue, delayMs) {
|
|
@@ -1309,20 +1380,22 @@ var SerialQueue = class {
|
|
|
1309
1380
|
};
|
|
1310
1381
|
|
|
1311
1382
|
// src/rabbitmq-transport.ts
|
|
1312
|
-
var
|
|
1313
|
-
var RabbitMqTransport = class {
|
|
1383
|
+
var RabbitMqTransport = class extends internal.SubscribingTransport {
|
|
1314
1384
|
name;
|
|
1315
1385
|
system = "rabbitmq";
|
|
1316
1386
|
#settings;
|
|
1317
1387
|
#supervisor;
|
|
1388
|
+
#publishing;
|
|
1318
1389
|
/** Serializes topology work so a subscription never races a reconnect. */
|
|
1319
1390
|
#topologyWork = new SerialQueue();
|
|
1320
1391
|
#live;
|
|
1321
1392
|
#subscription;
|
|
1322
1393
|
#closing;
|
|
1323
1394
|
constructor(options) {
|
|
1395
|
+
super();
|
|
1324
1396
|
this.#settings = resolveSettings(options);
|
|
1325
1397
|
this.name = this.#settings.name;
|
|
1398
|
+
this.#publishing = new ConfirmedPublishing(this.name, this.#settings.publishTimeoutMs);
|
|
1326
1399
|
this.#supervisor = new ConnectionSupervisor({
|
|
1327
1400
|
connect: this.#settings.connect,
|
|
1328
1401
|
reconnect: this.#settings.reconnect,
|
|
@@ -1364,7 +1437,7 @@ var RabbitMqTransport = class {
|
|
|
1364
1437
|
async publish(envelope, destination) {
|
|
1365
1438
|
assertShortString("Destination (routing key)", destination);
|
|
1366
1439
|
const { content, options } = this.#settings.encode(envelope);
|
|
1367
|
-
await this.#
|
|
1440
|
+
await this.#publishing.publish(this.#live?.publisher, {
|
|
1368
1441
|
exchange: this.#settings.exchange,
|
|
1369
1442
|
routingKey: destination,
|
|
1370
1443
|
content,
|
|
@@ -1372,63 +1445,16 @@ var RabbitMqTransport = class {
|
|
|
1372
1445
|
mandatory: !this.#settings.allowUnroutable
|
|
1373
1446
|
});
|
|
1374
1447
|
}
|
|
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
1448
|
/**
|
|
1426
1449
|
* Binds one durable quorum queue to `destinations` and delivers its messages
|
|
1427
1450
|
* to `onMessage`. Call it once per transport; give each subscription its own
|
|
1428
1451
|
* transport instance, with its own `name` and `consumerGroup`.
|
|
1429
1452
|
*/
|
|
1430
|
-
|
|
1431
|
-
|
|
1453
|
+
async openSubscription({
|
|
1454
|
+
destinations,
|
|
1455
|
+
onMessage,
|
|
1456
|
+
hooks
|
|
1457
|
+
}) {
|
|
1432
1458
|
if (this.#subscription !== void 0) {
|
|
1433
1459
|
throw new core.ConfigurationError(
|
|
1434
1460
|
`Transport '${this.name}' already has a subscription; create another transport for another consumer group`
|
|
@@ -1439,34 +1465,8 @@ var RabbitMqTransport = class {
|
|
|
1439
1465
|
}
|
|
1440
1466
|
const queue = resolveQueueName(this.#settings);
|
|
1441
1467
|
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
|
-
})
|
|
1468
|
+
topology: this.#topologyOf(queue, destinations),
|
|
1469
|
+
consumer: this.#createConsumer(queue, { onMessage, hooks })
|
|
1470
1470
|
};
|
|
1471
1471
|
await this.#topologyWork.run(async () => {
|
|
1472
1472
|
this.#subscription = subscription;
|
|
@@ -1475,6 +1475,44 @@ var RabbitMqTransport = class {
|
|
|
1475
1475
|
}
|
|
1476
1476
|
});
|
|
1477
1477
|
}
|
|
1478
|
+
#topologyOf(queue, destinations) {
|
|
1479
|
+
return {
|
|
1480
|
+
exchange: this.#settings.exchange,
|
|
1481
|
+
queue,
|
|
1482
|
+
deadLetterExchange: this.#settings.deadLetterExchange,
|
|
1483
|
+
deadLetterQueue: this.#settings.deadLetterQueue ?? `${queue}.dead`,
|
|
1484
|
+
deliveryLimit: this.#settings.deliveryLimit,
|
|
1485
|
+
ordered: this.#settings.ordered,
|
|
1486
|
+
retryDelaysMs: this.#settings.retryLadder,
|
|
1487
|
+
destinations
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
#createConsumer(queue, { onMessage, hooks }) {
|
|
1491
|
+
return new QueueConsumer({
|
|
1492
|
+
queue,
|
|
1493
|
+
prefetch: this.#settings.prefetch,
|
|
1494
|
+
handlerRetry: this.#settings.handlerRetry,
|
|
1495
|
+
redelivery: this.#settings.redelivery,
|
|
1496
|
+
retryLadder: this.#settings.retryLadder,
|
|
1497
|
+
redeliveryMaxAgeMs: this.#settings.redeliveryMaxAgeMs,
|
|
1498
|
+
ordered: this.#settings.ordered,
|
|
1499
|
+
deadLetterExchange: this.#settings.deadLetterExchange,
|
|
1500
|
+
republish: ({ exchange, routingKey }, { content, options }) => this.#publishing.publish(this.#live?.publisher, {
|
|
1501
|
+
exchange,
|
|
1502
|
+
routingKey,
|
|
1503
|
+
content,
|
|
1504
|
+
options,
|
|
1505
|
+
mandatory: true
|
|
1506
|
+
}),
|
|
1507
|
+
shutdownTimeoutMs: this.#settings.shutdownTimeoutMs,
|
|
1508
|
+
onMessage,
|
|
1509
|
+
...hooks === void 0 ? {} : { onInvalidMessage: hooks.onInvalidMessage },
|
|
1510
|
+
decode: this.#settings.decode,
|
|
1511
|
+
onConsumerLost: (reason) => this.#supervisor.recover(reason),
|
|
1512
|
+
logger: this.#settings.logger,
|
|
1513
|
+
random: this.#settings.random
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1478
1516
|
/**
|
|
1479
1517
|
* Stops consuming, waits up to `shutdownTimeout` for running handlers
|
|
1480
1518
|
* (messages still being retried go back to the queue), then closes the
|
|
@@ -1544,36 +1582,7 @@ var RabbitMqTransport = class {
|
|
|
1544
1582
|
this.#live = void 0;
|
|
1545
1583
|
this.#subscription?.consumer.detach();
|
|
1546
1584
|
}
|
|
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
1585
|
};
|
|
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
1586
|
|
|
1578
1587
|
Object.defineProperty(exports, "fromHeader", {
|
|
1579
1588
|
enumerable: true,
|