@palmetto/pubsub 1.0.7 → 1.1.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.
@@ -1,3 +1,4 @@
1
+ import { AmqpConnectionManagerOptions } from "amqp-connection-manager";
1
2
  import { MessageContext, PubSubConfiguration } from "../interfaces.js";
2
3
  export interface RabbitMqConnectionConfig {
3
4
  /**
@@ -12,13 +13,13 @@ export interface RabbitMqConnectionConfig {
12
13
  * Timeout for publisher startup retries if the connection fails (default: 10 seconds)
13
14
  */
14
15
  startupRetryTimeoutMs?: number;
16
+ /**
17
+ * Override default amqp-connection-manager options (heartbeatIntervalInSeconds: 60, reconnectTimeInSeconds: 5)
18
+ */
19
+ options?: AmqpConnectionManagerOptions;
15
20
  }
16
21
  export type QueueType = "default" | "dead-letter" | "retry";
17
- export type ExchangeType = "direct" | "topic";
18
- /**
19
- * Default binding key for all messages
20
- */
21
- export declare const DEFAULT_BINDING_KEY = "default";
22
+ export type ExchangeType = "direct" | "topic" | "fanout";
22
23
  export interface RabbitQueueExchangeConfiguration extends PubSubConfiguration {
23
24
  /**
24
25
  * The queue name prefix
@@ -29,11 +30,11 @@ export interface RabbitQueueExchangeConfiguration extends PubSubConfiguration {
29
30
  */
30
31
  topicSubscriberName?: string;
31
32
  /**
32
- * Support direct exchange where each message is delivered once, or topic exchange where each message is delivered to multiple queues
33
+ * Support direct exchange where each message is delivered once, or topic/fanout exchange where each message is delivered to multiple queues
33
34
  */
34
- exchangeType?: ExchangeType;
35
+ exchangeType: ExchangeType;
35
36
  /**
36
- * When true, the queue and exchange will be deleted after the consumers exit [note: dead-letter exchanges & queues main remain when there are no dead-letter messages or consumers]
37
+ * When true, the queue and exchange will be deleted after the consumers exit [note: dead-letter exchanges & queues may remain when there are no dead-letter messages or consumers]
37
38
  */
38
39
  temporary?: boolean;
39
40
  /**
@@ -78,6 +79,7 @@ export interface RabbitMqMessageContext extends MessageContext {
78
79
  * @returns The queue name
79
80
  */
80
81
  export declare function getQueueName(config: RabbitQueueExchangeConfiguration): string;
82
+ export declare const ExchangeNameExtensions: Record<QueueType, string | undefined>;
81
83
  export declare const QueueNameExtensions: Record<QueueType, string | undefined>;
82
84
  /**
83
85
  * Returns the exchange name based on the configuration
@@ -1,16 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.QueueNameExtensions = exports.DEFAULT_BINDING_KEY = void 0;
3
+ exports.QueueNameExtensions = exports.ExchangeNameExtensions = void 0;
4
4
  exports.getQueueName = getQueueName;
5
5
  exports.getExchangeName = getExchangeName;
6
6
  exports.getQueueType = getQueueType;
7
7
  exports.getRoutingKey = getRoutingKey;
8
8
  exports.getExchangeType = getExchangeType;
9
9
  const errors_js_1 = require("../errors.js");
10
- /**
11
- * Default binding key for all messages
12
- */
13
- exports.DEFAULT_BINDING_KEY = "default";
14
10
  /**
15
11
  * Returns the queue name based on the configuration
16
12
  *
@@ -24,25 +20,28 @@ function getQueueName(config) {
24
20
  return config.overrides.names[queueType].queueName;
25
21
  }
26
22
  let queueName = config.name;
27
- if (config.exchangeType === "topic") {
23
+ if (config.exchangeType === "topic" || config.exchangeType === "fanout") {
28
24
  if (!config.topicSubscriberName) {
29
- throw new errors_js_1.ConfigurationError("Topic queues must have a subscriber name");
25
+ throw new errors_js_1.ConfigurationError("Topic/Fanout queues must have a subscriber name");
30
26
  }
31
27
  queueName += "." + config.topicSubscriberName;
32
28
  }
33
- if (queueType === "dead-letter") {
34
- queueName += ".dlq";
35
- }
36
- else if (queueType === "retry") {
37
- queueName += ".rtq";
29
+ const ext = exports.QueueNameExtensions[queueType];
30
+ if (ext) {
31
+ queueName += ext;
38
32
  }
39
33
  return queueName;
40
34
  }
41
- exports.QueueNameExtensions = {
35
+ exports.ExchangeNameExtensions = {
42
36
  "dead-letter": ".dlx",
43
37
  retry: ".rtx",
44
38
  default: undefined,
45
39
  };
40
+ exports.QueueNameExtensions = {
41
+ "dead-letter": ".dlq",
42
+ retry: ".rtq",
43
+ default: undefined,
44
+ };
46
45
  /**
47
46
  * Returns the exchange name based on the configuration
48
47
  * @param config
@@ -56,14 +55,14 @@ function getExchangeName(config) {
56
55
  }
57
56
  let exchangeName = config.name;
58
57
  if (["dead-letter", "retry"].includes(queueType)) {
59
- if (config.exchangeType === "topic") {
58
+ if (config.exchangeType === "topic" || config.exchangeType === "fanout") {
60
59
  if (!config.topicSubscriberName) {
61
60
  throw new errors_js_1.ConfigurationError(`Topic ${queueType} exchanges must have a subscriber name`);
62
61
  }
63
62
  exchangeName += "." + config.topicSubscriberName;
64
63
  }
65
64
  }
66
- const ext = exports.QueueNameExtensions[queueType];
65
+ const ext = exports.ExchangeNameExtensions[queueType];
67
66
  if (ext) {
68
67
  exchangeName += ext;
69
68
  }
@@ -74,9 +73,21 @@ function getQueueType(config) {
74
73
  }
75
74
  function getRoutingKey(config) {
76
75
  var _a, _b;
77
- return (((_b = (_a = config.overrides) === null || _a === void 0 ? void 0 : _a.names[getQueueType(config)]) === null || _b === void 0 ? void 0 : _b.routingKey) ||
78
- exports.DEFAULT_BINDING_KEY);
76
+ const queueType = getQueueType(config);
77
+ const configuredRoutingKey = (_b = (_a = config.overrides) === null || _a === void 0 ? void 0 : _a.names[queueType]) === null || _b === void 0 ? void 0 : _b.routingKey;
78
+ if (configuredRoutingKey) {
79
+ return configuredRoutingKey;
80
+ }
81
+ const exchangeType = getExchangeType(config);
82
+ switch (exchangeType) {
83
+ case "direct":
84
+ return "default";
85
+ case "topic":
86
+ return "#";
87
+ case "fanout":
88
+ return "";
89
+ }
79
90
  }
80
91
  function getExchangeType(config) {
81
- return config.exchangeType || "direct";
92
+ return getQueueType(config) === "default" ? config.exchangeType : "fanout";
82
93
  }
@@ -27,11 +27,15 @@ exports.RABBITMQ_TRANSPORT = "rabbitmq";
27
27
  class RabbitMqConnection {
28
28
  static create(config, logger) {
29
29
  return __awaiter(this, void 0, void 0, function* () {
30
+ var _a;
30
31
  const AmqpMgrPackage = yield (0, lazy_load_js_1.lazyLoad)({
31
32
  packageName: "amqp-connection-manager",
32
33
  context: "RabbitMqConnection",
33
34
  });
34
- return new RabbitMqConnection(config, AmqpMgrPackage.connect(config.host), logger);
35
+ return new RabbitMqConnection(config, AmqpMgrPackage.connect(config.host, (_a = config.options) !== null && _a !== void 0 ? _a : {
36
+ heartbeatIntervalInSeconds: 60,
37
+ reconnectTimeInSeconds: 5,
38
+ }), logger);
35
39
  });
36
40
  }
37
41
  constructor(config, connection, logger) {
@@ -68,7 +72,7 @@ class RabbitMqConnection {
68
72
  }
69
73
  assertQueueAndBindings(channel, config) {
70
74
  return __awaiter(this, void 0, void 0, function* () {
71
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
75
+ var _a, _b, _c;
72
76
  let dlConfig, retryConfig;
73
77
  ({ config, dlConfig, retryConfig } = RabbitMqConnection.getConfigs(config));
74
78
  const exchangeName = yield this.assertExchange(channel, config);
@@ -81,8 +85,8 @@ class RabbitMqConnection {
81
85
  // special case to ensure the retry dead-letter only reverts to the original queue,
82
86
  // regardless of original exchange type [that is, not re-using the original queue exchange here]
83
87
  const retryDlExchangeName = ((_a = config.overrides) === null || _a === void 0 ? void 0 : _a.retryQueueExchangeName) ||
84
- `${queueName}${config_js_1.QueueNameExtensions["dead-letter"]}${config_js_1.QueueNameExtensions["retry"]}`;
85
- yield channel.assertExchange(retryDlExchangeName, "direct", {
88
+ `${queueName}${config_js_1.QueueNameExtensions["retry"]}${config_js_1.ExchangeNameExtensions["dead-letter"]}`;
89
+ yield channel.assertExchange(retryDlExchangeName, "fanout", {
86
90
  autoDelete: !!config.temporary,
87
91
  });
88
92
  (_c = (_b = this.logger).debug) === null || _c === void 0 ? void 0 : _c.call(_b, `Asserted exchange ${retryDlExchangeName}`);
@@ -93,13 +97,13 @@ class RabbitMqConnection {
93
97
  // create the default queue using the dead-letter exchange as the DLX
94
98
  yield this.assertQueue(channel, queueName, autoDelete, dlExchangeName);
95
99
  // bind the default queue using the default exchange and default routing key
96
- yield this.bindQueue(channel, queueName, exchangeName, ((_e = (_d = config.overrides) === null || _d === void 0 ? void 0 : _d.names.default) === null || _e === void 0 ? void 0 : _e.routingKey) || config_js_1.DEFAULT_BINDING_KEY);
97
- // bind the dead-letter queue to the dead-letter exchange and dead-letter routing key
98
- yield this.bindQueue(channel, dlQueueName, dlExchangeName, ((_g = (_f = config.overrides) === null || _f === void 0 ? void 0 : _f.names["dead-letter"]) === null || _g === void 0 ? void 0 : _g.routingKey) || config_js_1.DEFAULT_BINDING_KEY);
99
- // bind the retry queue to the retry exchange and retry routing key
100
- yield this.bindQueue(channel, retryQueueName, retryExchangeName, ((_j = (_h = config.overrides) === null || _h === void 0 ? void 0 : _h.names.retry) === null || _j === void 0 ? void 0 : _j.routingKey) || config_js_1.DEFAULT_BINDING_KEY);
101
- // bind the queue to the retry dead-letter exchange and default routing key
102
- yield this.bindQueue(channel, queueName, retryDlExchangeName, ((_l = (_k = config.overrides) === null || _k === void 0 ? void 0 : _k.names.default) === null || _l === void 0 ? void 0 : _l.routingKey) || config_js_1.DEFAULT_BINDING_KEY);
100
+ yield this.bindQueue(channel, queueName, exchangeName, (0, config_js_1.getRoutingKey)(config));
101
+ // bind the dead-letter queue to the dead-letter exchange
102
+ yield this.bindQueue(channel, dlQueueName, dlExchangeName, "");
103
+ // bind the retry queue to the retry exchange
104
+ yield this.bindQueue(channel, retryQueueName, retryExchangeName, "");
105
+ // bind the queue to the retry dead-letter exchange
106
+ yield this.bindQueue(channel, queueName, retryDlExchangeName, "");
103
107
  return {
104
108
  exchangeName,
105
109
  dlExchangeName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@palmetto/pubsub",
3
- "version": "1.0.7",
3
+ "version": "1.1.0",
4
4
  "main": "./dist/main.js",
5
5
  "scripts": {
6
6
  "lint": "yarn run -T eslint --fix ./src",
@@ -125,26 +125,37 @@ const config: RabbitQueueExchangeConfiguration = {
125
125
  name: "my.queue",
126
126
  transport: RABBITMQ_TRANSPORT,
127
127
  schema: MyModelSchema,
128
+ exchangeType: "direct",
128
129
  };
129
130
  ```
130
131
 
131
132
  This configuration creates the following queues:
132
133
 
133
134
  - `my.queue` the main queue which receives all messages published using the config
134
- - dead-letter-exchange is set to `my.queue.dlx`
135
+ - `x-dead-letter-exchange` is set to `my.queue.dlx`
135
136
  - `my.queue.dlq` the main queue's dead-letter queue where failed messages end up
137
+ - bound to `my.queue.dlx` exchange
136
138
  - `my.queue.rtq` the main queue's retry queue where messages to be retried end up
137
- - dead-letter-exchange is set to the main queue's exchange so that messages are retried
139
+ - bound to `my.queue.rtx` exchange
140
+ - `x-dead-letter-exchange` is set to `my.queue.rtq.dlx`
141
+ - messages delivered here have an expiration
142
+ - when the message expires, it is removed from the queue and sent to the `my.queue.rtq.dlx` exchange which publishes the message back to the main queue `my.queue`
138
143
 
139
- And these direct exchanges:
144
+ And these exchanges:
140
145
 
141
146
  - `my.queue` the main exchange that is bound to the main queue
142
- - `my.queue.dlx` the dead-letter exchange that receives failed messages from the main queue
143
- - `my.queue.rtx` the retry exchange that receives messages to retry. it is bound to the retry queue
144
-
145
- ### Topic exchanges
146
-
147
- Topic exchanges provide the ability to use a single exchange to copy messages to multiple queues simultaneously. When subscribing to a topic exchange, you must also provide a subscriber name to include in the queue name. This ensures that there is a unique queue for each subscriber of the exchange.
147
+ - `my.queue.dlx` a fanout dead-letter exchange that receives failed messages from the main queue
148
+ - binding made to the `my.queue.dlq`
149
+ - configured as the `x-dead-letter-exchange` for the main queue `my.queue`
150
+ - `my.queue.rtx` a fanout retry exchange that receives messages to retry. it is bound to the retry queue
151
+ - binding made to the `my.queue.rtq`
152
+ - `my.queue.rtq.dlx` a fanout dead-letter exchange that receives retry messages from the retry queue
153
+ - binding made back to the original `my.queue`
154
+ - configured as the `x-dead-letter-exchange` for the retry queue `my.queue.rtq`
155
+
156
+ ### Topic/Fanout exchanges
157
+
158
+ Topic/Fanout exchanges provide the ability to use a single exchange to copy messages to multiple queues simultaneously. When subscribing to a topic exchange, you must also provide a subscriber name to include in the queue name. This ensures that there is a unique queue for each subscriber of the exchange.
148
159
 
149
160
  ```ts
150
161
  const config: RabbitQueueExchangeConfiguration = {