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