@palmetto/pubsub 1.0.7 → 2.0.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/README.md +8 -8
- package/dist/interfaces.d.ts +17 -17
- package/dist/interfaces.js +7 -7
- package/dist/publisher.js +8 -4
- package/dist/rabbitmq/config.d.ts +10 -8
- package/dist/rabbitmq/config.js +29 -18
- package/dist/rabbitmq/connection.js +15 -11
- package/dist/subscriber.js +1 -1
- package/package.json +3 -3
- package/src/rabbitmq/README.md +20 -9
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ yarn add @palmetto/pubsub zod
|
|
|
27
27
|
1. Create a publisher and send a message
|
|
28
28
|
|
|
29
29
|
```ts
|
|
30
|
-
import { z
|
|
30
|
+
import { z } from "zod";
|
|
31
31
|
import {
|
|
32
32
|
IdMetaSchema,
|
|
33
33
|
Publisher,
|
|
@@ -39,7 +39,7 @@ yarn add @palmetto/pubsub zod
|
|
|
39
39
|
const rabbitMqPublisher = new RabbitMqPublisher(...);
|
|
40
40
|
|
|
41
41
|
const schema = IdMetaSchema.extend({
|
|
42
|
-
message:
|
|
42
|
+
message: z.string(),
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
const Model = z.infer<typeof schema>;
|
|
@@ -48,7 +48,7 @@ yarn add @palmetto/pubsub zod
|
|
|
48
48
|
name: "TestModel",
|
|
49
49
|
schema: TestModelSchema,
|
|
50
50
|
transport: RABBITMQ_TRANSPORT,
|
|
51
|
-
|
|
51
|
+
exchangeType: "topic",
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
const publisher = new Publisher(console, [rabbitMqPublisher]);
|
|
@@ -65,7 +65,7 @@ yarn add @palmetto/pubsub zod
|
|
|
65
65
|
1. Create subscriber and subscriber to a message
|
|
66
66
|
|
|
67
67
|
```ts
|
|
68
|
-
import { z
|
|
68
|
+
import { z } from "zod";
|
|
69
69
|
import {
|
|
70
70
|
BaseMessage,
|
|
71
71
|
IdMetaSchema,
|
|
@@ -79,8 +79,8 @@ yarn add @palmetto/pubsub zod
|
|
|
79
79
|
const rabbitMqSubscriber = new RabbitMqSubscriber(...);
|
|
80
80
|
|
|
81
81
|
const schema = IdMetaSchema.extend({
|
|
82
|
-
message:
|
|
83
|
-
code:
|
|
82
|
+
message: z.string(),
|
|
83
|
+
code: z.string(),
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
const Model = z.infer<typeof schema>;
|
|
@@ -91,7 +91,7 @@ yarn add @palmetto/pubsub zod
|
|
|
91
91
|
retryDelay: 1_000,
|
|
92
92
|
retries: 20,
|
|
93
93
|
transport: RABBITMQ_TRANSPORT,
|
|
94
|
-
|
|
94
|
+
exchangeType: "fanout",
|
|
95
95
|
};
|
|
96
96
|
|
|
97
97
|
function onMessage(message: Model) {
|
|
@@ -116,7 +116,7 @@ For specific details about using BullMQ see [BullMQ README.md](src/bullmq/README
|
|
|
116
116
|
|
|
117
117
|
### Defining schemas
|
|
118
118
|
|
|
119
|
-
All schemas must be defined using `zod
|
|
119
|
+
All schemas must be defined using `zod` v4.1+. By convention, all messages should extend the `IdMetaSchema` schema, but it is not strictly required.
|
|
120
120
|
|
|
121
121
|
IdMetaSchema provides several fields useful for message tracking:
|
|
122
122
|
|
package/dist/interfaces.d.ts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { z
|
|
2
|
-
export declare const MetaSchema:
|
|
3
|
-
createdAt:
|
|
4
|
-
schemaId:
|
|
5
|
-
publishedBy:
|
|
6
|
-
},
|
|
7
|
-
export declare const IdMetaSchema:
|
|
8
|
-
id:
|
|
9
|
-
meta:
|
|
10
|
-
createdAt:
|
|
11
|
-
schemaId:
|
|
12
|
-
publishedBy:
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
export type Meta =
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const MetaSchema: z.ZodObject<{
|
|
3
|
+
createdAt: z.ZodISODateTime;
|
|
4
|
+
schemaId: z.ZodString;
|
|
5
|
+
publishedBy: z.ZodString;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
export declare const IdMetaSchema: z.ZodObject<{
|
|
8
|
+
id: z.ZodOptional<z.ZodString>;
|
|
9
|
+
meta: z.ZodOptional<z.ZodObject<{
|
|
10
|
+
createdAt: z.ZodISODateTime;
|
|
11
|
+
schemaId: z.ZodString;
|
|
12
|
+
publishedBy: z.ZodString;
|
|
13
|
+
}, z.core.$strip>>;
|
|
14
|
+
}, z.core.$strip>;
|
|
15
|
+
export type Meta = z.infer<typeof MetaSchema>;
|
|
16
16
|
/**
|
|
17
17
|
* All messages should be based on this schema
|
|
18
18
|
*/
|
|
19
|
-
export type BaseMessage =
|
|
19
|
+
export type BaseMessage = z.infer<typeof IdMetaSchema>;
|
|
20
20
|
/**
|
|
21
21
|
* All pubsub publishers must implement this interface
|
|
22
22
|
*/
|
|
@@ -37,7 +37,7 @@ export interface PubSubConfiguration {
|
|
|
37
37
|
/**
|
|
38
38
|
* The schema for the message - the message is verfied during publish and subscribe
|
|
39
39
|
*/
|
|
40
|
-
schema:
|
|
40
|
+
schema: z.ZodType;
|
|
41
41
|
/**
|
|
42
42
|
* The message transport to use (defined by the event-pubsub implementations)
|
|
43
43
|
*/
|
package/dist/interfaces.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MessageResult = exports.IdMetaSchema = exports.MetaSchema = void 0;
|
|
4
|
-
const
|
|
5
|
-
exports.MetaSchema =
|
|
6
|
-
createdAt:
|
|
7
|
-
schemaId:
|
|
8
|
-
publishedBy:
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
exports.MetaSchema = zod_1.z.object({
|
|
6
|
+
createdAt: zod_1.z.iso.datetime(),
|
|
7
|
+
schemaId: zod_1.z.string(),
|
|
8
|
+
publishedBy: zod_1.z.string(),
|
|
9
9
|
});
|
|
10
|
-
exports.IdMetaSchema =
|
|
11
|
-
id:
|
|
10
|
+
exports.IdMetaSchema = zod_1.z.object({
|
|
11
|
+
id: zod_1.z.string().optional(),
|
|
12
12
|
meta: exports.MetaSchema.optional(),
|
|
13
13
|
});
|
|
14
14
|
var MessageResult;
|
package/dist/publisher.js
CHANGED
|
@@ -10,18 +10,22 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.Publisher = void 0;
|
|
13
|
-
const
|
|
13
|
+
const zod_1 = require("zod");
|
|
14
14
|
const uuid_1 = require("uuid");
|
|
15
15
|
const crypto_1 = require("crypto");
|
|
16
16
|
const errors_js_1 = require("./errors.js");
|
|
17
17
|
class Publisher {
|
|
18
18
|
constructor(logger, providers) {
|
|
19
|
+
var _a, _b;
|
|
19
20
|
this.logger = logger;
|
|
20
21
|
this.hashes = new Map();
|
|
21
22
|
this.publisherProviders = new Map();
|
|
22
23
|
if (providers) {
|
|
23
24
|
providers.forEach((provider) => this.publisherProviders.set(provider.transport, provider));
|
|
24
25
|
}
|
|
26
|
+
(_b = (_a = this.logger).debug) === null || _b === void 0 ? void 0 : _b.call(_a, `Publisher initialized with providers: ${[
|
|
27
|
+
...this.publisherProviders.keys(),
|
|
28
|
+
].join(", ")}`);
|
|
25
29
|
}
|
|
26
30
|
addProvider(provider) {
|
|
27
31
|
var _a, _b;
|
|
@@ -64,12 +68,12 @@ class Publisher {
|
|
|
64
68
|
else {
|
|
65
69
|
message.meta.schemaId = schemaId;
|
|
66
70
|
}
|
|
67
|
-
const
|
|
68
|
-
const check = schema.safeParse(JSON.parse(json));
|
|
71
|
+
const check = schema.safeEncode(message);
|
|
69
72
|
if (!check.success) {
|
|
70
73
|
throw new errors_js_1.SchemaValidationError(`Schema did not accept the published message: ${check.error.message}`);
|
|
71
74
|
}
|
|
72
75
|
(_b = (_a = this.logger).debug) === null || _b === void 0 ? void 0 : _b.call(_a, `Publisher publishing message for ${provider.transport}`);
|
|
76
|
+
const json = JSON.stringify(check.data);
|
|
73
77
|
yield provider.publish(config, json);
|
|
74
78
|
});
|
|
75
79
|
}
|
|
@@ -92,7 +96,7 @@ class Publisher {
|
|
|
92
96
|
const { schema } = config;
|
|
93
97
|
let hash = this.hashes.get(schema);
|
|
94
98
|
if (!hash) {
|
|
95
|
-
const jsonSchema = JSON.stringify(
|
|
99
|
+
const jsonSchema = JSON.stringify(zod_1.z.toJSONSchema(schema, { io: "input" }), null, 3);
|
|
96
100
|
hash = (0, crypto_1.createHash)("sha256").update(jsonSchema).digest("hex");
|
|
97
101
|
this.hashes.set(schema, hash);
|
|
98
102
|
}
|
|
@@ -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
|
|
35
|
+
exchangeType: ExchangeType;
|
|
35
36
|
/**
|
|
36
|
-
* When true, the queue and exchange will be deleted after the consumers exit [note: dead-letter exchanges & queues
|
|
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
|
package/dist/rabbitmq/config.js
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.QueueNameExtensions = exports.
|
|
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
|
-
|
|
34
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
78
|
-
|
|
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
|
|
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
|
|
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
|
|
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["
|
|
85
|
-
yield channel.assertExchange(retryDlExchangeName, "
|
|
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, (
|
|
97
|
-
// bind the dead-letter queue to the dead-letter exchange
|
|
98
|
-
yield this.bindQueue(channel, dlQueueName, dlExchangeName,
|
|
99
|
-
// bind the retry queue to the retry exchange
|
|
100
|
-
yield this.bindQueue(channel, retryQueueName, retryExchangeName,
|
|
101
|
-
// bind the queue to the retry dead-letter exchange
|
|
102
|
-
yield this.bindQueue(channel, queueName, retryDlExchangeName,
|
|
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/dist/subscriber.js
CHANGED
|
@@ -57,7 +57,7 @@ class Subscriber {
|
|
|
57
57
|
var _a;
|
|
58
58
|
const start = new Date().valueOf();
|
|
59
59
|
const jsonObject = JSON.parse(jsonStr);
|
|
60
|
-
const r = schema.
|
|
60
|
+
const r = schema.safeDecode(jsonObject);
|
|
61
61
|
if (!r.success) {
|
|
62
62
|
const id = interfaces_js_1.IdMetaSchema.safeParse(jsonObject);
|
|
63
63
|
if (id.success) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@palmetto/pubsub",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"main": "./dist/main.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"lint": "yarn run -T eslint --fix ./src",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"ts-node": "^10.9.2",
|
|
29
29
|
"typescript": "^5.8.3",
|
|
30
30
|
"vitest": "^3.2.4",
|
|
31
|
-
"zod": "^4.
|
|
31
|
+
"zod": "^4.1.13"
|
|
32
32
|
},
|
|
33
33
|
"files": [
|
|
34
34
|
"dist/**/*",
|
|
@@ -47,6 +47,6 @@
|
|
|
47
47
|
"amqp-connection-manager": "^4.1.14",
|
|
48
48
|
"amqplib": "^0.10.8",
|
|
49
49
|
"bullmq": "^5.58.0",
|
|
50
|
-
"zod": "^
|
|
50
|
+
"zod": "^4.1.13"
|
|
51
51
|
}
|
|
52
52
|
}
|
package/src/rabbitmq/README.md
CHANGED
|
@@ -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
|
-
-
|
|
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
|
|
144
|
+
And these exchanges:
|
|
140
145
|
|
|
141
146
|
- `my.queue` the main exchange that is bound to the main queue
|
|
142
|
-
- `my.queue.dlx`
|
|
143
|
-
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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 = {
|