@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 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
- for (let index = 0; index < value.length; index += 1) {
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
- const prototype = Object.getPrototypeOf(value);
292
- if (prototype !== Object.prototype && prototype !== null) {
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 not a plain object (class instance, Date, Map or Set)"
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
- routingKey: message.fields.routingKey,
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 = resolveText(options.id, { message: plain, name: "id" }) ?? nonEmptyText(properties.messageId);
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: resolveText(options.source, { message: plain, name: "source" }) ?? defaultSource(message),
572
- key: resolveText(options.key, { message: plain, name: "key" }) ?? null,
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 completeDecodedEvent(decoded, message) {
582
- if (typeof decoded !== "object" || decoded === null) {
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
- id: event.id,
592
- type: event.type,
593
- source: event.source ?? defaultSource(message),
594
- key: event.key ?? null,
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 = nonEmpty(options.exchange ?? "relaybus", "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.assertPositiveInteger(
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: nonEmpty(options.name ?? "rabbitmq", "name"),
958
+ name: internal.NonEmptyText.of(options.name ?? "rabbitmq", "name"),
866
959
  connect: resolveConnect(options),
867
960
  exchange,
868
961
  prefetch,
869
- queuePrefix: nonEmpty(options.queuePrefix ?? "relaybus", "queuePrefix"),
870
- consumerGroup: optionalNonEmpty(options.consumerGroup, "consumerGroup"),
871
- queue: optionalNonEmpty(options.queue, "queue"),
872
- deadLetterExchange: nonEmpty(
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: optionalNonEmpty(options.deadLetterQueue, "deadLetterQueue"),
877
- deliveryLimit: internal.assertPositiveInteger(
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: positiveDuration(
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: positiveDuration(options.publishTimeout ?? "30s", "publishTimeout"),
901
- shutdownTimeoutMs: core.toMilliseconds(options.shutdownTimeout ?? "30s", "shutdownTimeout"),
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) => completeDecodedEvent(chosen.decode(message), 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 NACK_MESSAGE = "message nacked";
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.#publishConfirmed({
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
- // eslint-disable-next-line relaybus/max-two-params -- the Transport.subscribe signature
1431
- async subscribe(destinations, onMessage, hooks) {
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
- exchange: this.#settings.exchange,
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