codeweaver 3.1.2 → 4.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 +56 -73
- package/package.json +23 -1
- package/src/config.ts +17 -15
- package/src/constants.ts +1 -0
- package/src/core/aws/api-gateway.ts +187 -0
- package/src/core/aws/basic-types.ts +147 -0
- package/src/core/aws/dynamodb.ts +187 -0
- package/src/core/aws/index.ts +9 -0
- package/src/core/aws/lambda.ts +199 -0
- package/src/core/aws/message-broker.ts +167 -0
- package/src/core/aws/message.ts +259 -0
- package/src/core/aws/s3.ts +136 -0
- package/src/core/aws/utilities.ts +44 -0
- package/src/core/cache/basic-types.ts +17 -0
- package/src/core/cache/decorator.ts +72 -0
- package/src/core/cache/index.ts +4 -0
- package/src/core/cache/memory-cache.class.ts +119 -0
- package/src/{utilities/cache/redis-cache.ts → core/cache/redis-cache.class.ts} +58 -10
- package/src/core/container/basic-types.ts +10 -0
- package/src/{utilities → core/container}/container.ts +7 -17
- package/src/core/container/index.ts +2 -0
- package/src/{utilities → core/error}/error-handling.ts +1 -65
- package/src/core/error/index.ts +3 -0
- package/src/core/error/response-error.ts +45 -0
- package/src/core/error/send-http-error.ts +15 -0
- package/src/core/file/file-helpers.ts +166 -0
- package/src/core/file/index.ts +1 -0
- package/src/{utilities → core/helpers}/assignment.ts +2 -2
- package/src/core/helpers/comparison.ts +86 -0
- package/src/{utilities → core/helpers}/conversion.ts +2 -2
- package/src/core/helpers/decorators.ts +316 -0
- package/src/core/helpers/format.ts +9 -0
- package/src/core/helpers/index.ts +7 -0
- package/src/core/helpers/range.ts +67 -0
- package/src/core/helpers/types.ts +3 -0
- package/src/core/logger/index.ts +4 -0
- package/src/{utilities/logger/logger.config.ts → core/logger/winston-logger.config.ts} +1 -1
- package/src/{utilities → core}/logger/winston-logger.service.ts +3 -3
- package/src/core/message-broker/bullmq/basic-types.ts +67 -0
- package/src/core/message-broker/bullmq/broker.ts +141 -0
- package/src/core/message-broker/bullmq/index.ts +3 -0
- package/src/core/message-broker/bullmq/queue.ts +58 -0
- package/src/core/message-broker/bullmq/worker.ts +68 -0
- package/src/core/message-broker/kafka/basic-types.ts +45 -0
- package/src/core/message-broker/kafka/consumer.ts +95 -0
- package/src/core/message-broker/kafka/index.ts +3 -0
- package/src/core/message-broker/kafka/producer.ts +113 -0
- package/src/core/message-broker/rabitmq/basic-types.ts +44 -0
- package/src/core/message-broker/rabitmq/channel.ts +95 -0
- package/src/core/message-broker/rabitmq/consumer.ts +94 -0
- package/src/core/message-broker/rabitmq/index.ts +4 -0
- package/src/core/message-broker/rabitmq/producer.ts +100 -0
- package/src/core/message-broker/utilities.ts +50 -0
- package/src/core/middlewares/basic-types.ts +39 -0
- package/src/core/middlewares/decorators.ts +244 -0
- package/src/core/middlewares/index.ts +3 -0
- package/src/core/middlewares/middlewares.ts +246 -0
- package/src/core/parallel/index.ts +3 -0
- package/src/{utilities → core}/parallel/parallel.ts +11 -1
- package/src/core/rate-limit/basic-types.ts +43 -0
- package/src/core/rate-limit/index.ts +4 -0
- package/src/core/rate-limit/memory-store.ts +65 -0
- package/src/core/rate-limit/rate-limit.ts +134 -0
- package/src/core/rate-limit/redis-store.ts +141 -0
- package/src/core/retry/basic-types.ts +21 -0
- package/src/core/retry/decorator.ts +139 -0
- package/src/core/retry/index.ts +2 -0
- package/src/main.ts +6 -8
- package/src/routers/orders/index.router.ts +5 -1
- package/src/routers/orders/order.controller.ts +46 -59
- package/src/routers/products/index.router.ts +2 -1
- package/src/routers/products/product.controller.ts +25 -63
- package/src/routers/users/index.router.ts +1 -1
- package/src/routers/users/user.controller.ts +23 -51
- package/src/utilities/cache/memory-cache.ts +0 -74
- /package/src/{utilities → core}/logger/base-logger.interface.ts +0 -0
- /package/src/{utilities → core}/logger/logger.service.ts +0 -0
- /package/src/{utilities → core}/parallel/chanel.ts +0 -0
- /package/src/{utilities → core}/parallel/worker-pool.ts +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Queue, Job } from "bullmq";
|
|
2
|
+
import { ProducerConfig } from "./basic-types";
|
|
3
|
+
|
|
4
|
+
export class BullQueueProducer<T = any> {
|
|
5
|
+
private queue?: Queue;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates a new BullMQ producer.
|
|
9
|
+
* @param config - Configuration options for the producer.
|
|
10
|
+
*/
|
|
11
|
+
public constructor(private config: ProducerConfig) {}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns the BullMQ queue instance associated with this producer.
|
|
15
|
+
* If the queue has not been created yet, it will be created with the given connection options.
|
|
16
|
+
* @returns The BullMQ queue instance associated with this producer.
|
|
17
|
+
*/
|
|
18
|
+
private getQueue(): Queue {
|
|
19
|
+
if (!this.queue) {
|
|
20
|
+
this.queue = new Queue(this.config.queueName, {
|
|
21
|
+
connection: this.config.connection ?? undefined,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return this.queue!;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Adds a job to the queue.
|
|
29
|
+
* If a serializer is provided in the producer config, it will be used to serialize the data.
|
|
30
|
+
* Otherwise, the data will be passed as-is.
|
|
31
|
+
* @param data - The data to serialize and add to the queue.
|
|
32
|
+
* @param opts - Optional job options to add to the job.
|
|
33
|
+
* @returns A promise resolved with the added job.
|
|
34
|
+
*/
|
|
35
|
+
public async addJob(data: T, opts?: any): Promise<Job> {
|
|
36
|
+
// Serialize data if serializer provided, but Bull's data field is passed as is
|
|
37
|
+
const payload = this.config.serializer?.serialize
|
|
38
|
+
? this.config.serializer.serialize(data)
|
|
39
|
+
: data;
|
|
40
|
+
|
|
41
|
+
const queue = this.getQueue();
|
|
42
|
+
// You can pass the raw data; a serializer is mainly for transport
|
|
43
|
+
return await queue.add("job", payload, {
|
|
44
|
+
...(this.config.defaultJobOptions ?? {}),
|
|
45
|
+
...opts,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Closes the BullMQ queue associated with this producer.
|
|
51
|
+
* If the queue has not been created yet, this method does nothing.
|
|
52
|
+
* @returns A promise resolved when the queue has been closed.
|
|
53
|
+
*/
|
|
54
|
+
public async close(): Promise<void> {
|
|
55
|
+
await this.queue?.close?.();
|
|
56
|
+
this.queue = undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Worker, Job } from "bullmq";
|
|
2
|
+
import { ConsumerConfig } from "./basic-types";
|
|
3
|
+
|
|
4
|
+
export class BullWorker<T = any> {
|
|
5
|
+
private worker?: Worker;
|
|
6
|
+
private config: ConsumerConfig;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Constructor for a BullWorker.
|
|
10
|
+
* @param config - Configuration options for the worker.
|
|
11
|
+
*/
|
|
12
|
+
constructor(config: ConsumerConfig) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns the BullMQ worker associated with this worker.
|
|
18
|
+
* If the worker has not been created yet, it will be created with the given connection options.
|
|
19
|
+
* @returns The BullMQ worker associated with this worker.
|
|
20
|
+
*/
|
|
21
|
+
private getWorker(): Worker {
|
|
22
|
+
if (!this.worker) {
|
|
23
|
+
this.worker = new Worker(
|
|
24
|
+
this.config.queueName,
|
|
25
|
+
async (job: Job) => {
|
|
26
|
+
const data = this.config.serializer?.deserialize
|
|
27
|
+
? (this.config.serializer.deserialize(job.data) as T)
|
|
28
|
+
: (job.data as T);
|
|
29
|
+
await this.config.processor({
|
|
30
|
+
id: job.id,
|
|
31
|
+
data,
|
|
32
|
+
timestamp: Date.now(),
|
|
33
|
+
attemptsMade: job.attemptsMade,
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
connection: this.config.connection ?? undefined,
|
|
38
|
+
concurrency: this.config.concurrency ?? 1,
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return this.worker;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Starts the BullMQ worker.
|
|
47
|
+
* The worker will process all messages published to the associated queue.
|
|
48
|
+
* If the worker has not been created yet, it will be created with the given connection options.
|
|
49
|
+
* @returns A promise resolved when the worker has been started.
|
|
50
|
+
*/
|
|
51
|
+
public async start(): Promise<void> {
|
|
52
|
+
this.getWorker();
|
|
53
|
+
|
|
54
|
+
this.worker?.on("error", (err) => {
|
|
55
|
+
throw err;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Stops the BullMQ worker.
|
|
61
|
+
* If the worker has not been created yet, this method does nothing.
|
|
62
|
+
* @returns A promise resolved when the worker has been stopped.
|
|
63
|
+
*/
|
|
64
|
+
public async stop(): Promise<void> {
|
|
65
|
+
await this.worker?.close?.();
|
|
66
|
+
this.worker = undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { EachMessagePayload } from "kafkajs";
|
|
2
|
+
|
|
3
|
+
export type KafkaKey = string | Buffer;
|
|
4
|
+
export type KafkaHeaders = Record<string, string | Buffer>;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Kafka producer config
|
|
8
|
+
* @see https://kafka.js.org/docs/producer
|
|
9
|
+
*/
|
|
10
|
+
export interface ProducerConfig {
|
|
11
|
+
clientId?: string;
|
|
12
|
+
brokers: string[]; // ["host:port", ...]
|
|
13
|
+
retry?: {
|
|
14
|
+
retries?: number;
|
|
15
|
+
initialRetryTime?: number;
|
|
16
|
+
backoffMax?: number;
|
|
17
|
+
};
|
|
18
|
+
// enableIdempotence is available on producer in kafkajs
|
|
19
|
+
idempotent?: boolean;
|
|
20
|
+
// acks: -1, 1, 0
|
|
21
|
+
allowAutoTopicCreation?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Kafka message
|
|
26
|
+
*/
|
|
27
|
+
export interface Message<T = any> {
|
|
28
|
+
key?: KafkaKey;
|
|
29
|
+
value: T;
|
|
30
|
+
headers?: KafkaHeaders;
|
|
31
|
+
partition?: number;
|
|
32
|
+
timestamp?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Kafka consumer config
|
|
37
|
+
* @see https://kafka.js.org/docs/consumer
|
|
38
|
+
*/
|
|
39
|
+
export interface ConsumerConfig {
|
|
40
|
+
clientId?: string;
|
|
41
|
+
brokers: string[];
|
|
42
|
+
groupId: string;
|
|
43
|
+
fromBeginning?: boolean;
|
|
44
|
+
eachMessage?: (payload: EachMessagePayload) => Promise<void> | void;
|
|
45
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Kafka, Consumer, EachMessagePayload } from "kafkajs";
|
|
2
|
+
import { ConsumerConfig } from "./basic-types";
|
|
3
|
+
import { BrokerSerializer, getDefaultJsonSchema } from "../utilities";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Kafka consumer
|
|
7
|
+
* @see https://kafka.js.org/docs/consumer
|
|
8
|
+
*/
|
|
9
|
+
export class KafkaConsumer<T> {
|
|
10
|
+
private consumer?: Consumer;
|
|
11
|
+
private client: Kafka;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Creates a new Kafka consumer.
|
|
15
|
+
* @param config - Configuration options for the consumer.
|
|
16
|
+
* @param schema - Optional serializer for deserializing messages.
|
|
17
|
+
* @param messageHandler - Optional message handler to invoke when a message is received.
|
|
18
|
+
*/
|
|
19
|
+
public constructor(
|
|
20
|
+
public config: ConsumerConfig,
|
|
21
|
+
public schema?: BrokerSerializer<T>,
|
|
22
|
+
public messageHandler?: (
|
|
23
|
+
value: T,
|
|
24
|
+
payload: EachMessagePayload
|
|
25
|
+
) => Promise<void> | void
|
|
26
|
+
) {
|
|
27
|
+
this.client = new Kafka({
|
|
28
|
+
clientId: config.clientId ?? "kafka-consumer",
|
|
29
|
+
brokers: config.brokers,
|
|
30
|
+
});
|
|
31
|
+
this.schema = schema ?? getDefaultJsonSchema<T>();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Connects to the Kafka cluster.
|
|
36
|
+
* If the consumer is already connected, this method returns immediately.
|
|
37
|
+
* Otherwise, it creates a new consumer and connects to the Kafka cluster.
|
|
38
|
+
* @returns A promise resolved when the consumer is connected.
|
|
39
|
+
*/
|
|
40
|
+
public async connect(): Promise<void> {
|
|
41
|
+
if (this.consumer) return;
|
|
42
|
+
this.consumer = this.client.consumer({ groupId: this.config.groupId });
|
|
43
|
+
await this.consumer.connect();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Subscribes to the given topics.
|
|
48
|
+
* If the consumer is not already connected, it will first connect to the Kafka cluster.
|
|
49
|
+
* By default, the consumer will subscribe to the topics from the beginning.
|
|
50
|
+
* This can be overridden by passing "fromBeginning" in the config.
|
|
51
|
+
* @param topics - The topics to subscribe to.
|
|
52
|
+
* @returns A promise resolved when the consumer is subscribed to the topics.
|
|
53
|
+
*/
|
|
54
|
+
public async subscribeTopics(topics: string[]): Promise<void> {
|
|
55
|
+
if (!this.consumer) await this.connect();
|
|
56
|
+
for (const t of topics) {
|
|
57
|
+
// subscribe from beginning by default; can be overridden via eachTopic
|
|
58
|
+
await this.consumer.subscribe({
|
|
59
|
+
topic: t,
|
|
60
|
+
fromBeginning: this.config.fromBeginning ?? false,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Starts the consumer and begins consuming messages from the subscribed topics.
|
|
67
|
+
* If the consumer is not already connected, it will first connect to the Kafka cluster.
|
|
68
|
+
* @throws {Error} If the consumer is not connected.
|
|
69
|
+
*/
|
|
70
|
+
public async run(): Promise<void> {
|
|
71
|
+
if (!this.consumer)
|
|
72
|
+
throw new Error("Consumer not connected. Call connect() first.");
|
|
73
|
+
|
|
74
|
+
await this.consumer.run({
|
|
75
|
+
eachMessage: async (payload: EachMessagePayload) => {
|
|
76
|
+
await this.messageHandler?.(
|
|
77
|
+
this.schema.deserialize(payload.message.value),
|
|
78
|
+
payload
|
|
79
|
+
);
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Disconnects from the Kafka cluster and unsubscribes from all topics.
|
|
86
|
+
* If the consumer is not currently connected, this method does nothing.
|
|
87
|
+
* @returns A promise resolved when the consumer is disconnected.
|
|
88
|
+
*/
|
|
89
|
+
public async disconnect(): Promise<void> {
|
|
90
|
+
if (this.consumer) {
|
|
91
|
+
await this.consumer.disconnect();
|
|
92
|
+
this.consumer = undefined;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Kafka, Producer, ProducerRecord, KafkaConfig } from "kafkajs";
|
|
2
|
+
import { ProducerConfig, Message } from "./basic-types";
|
|
3
|
+
import { backoff, getDefaultJsonSchema, BrokerSerializer } from "../utilities";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Kafka producer
|
|
7
|
+
* @see https://kafka.js.org/docs/producer
|
|
8
|
+
*/
|
|
9
|
+
export class KafkaProducer<T = any> {
|
|
10
|
+
private producer?: Producer;
|
|
11
|
+
private client: Kafka;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Creates a new Kafka producer.
|
|
15
|
+
* @param config - Configuration options for the producer.
|
|
16
|
+
* @param schema - Optional serializer for deserializing messages.
|
|
17
|
+
* @see https://kafka.js.org/docs/producer
|
|
18
|
+
*/
|
|
19
|
+
public constructor(
|
|
20
|
+
public config: ProducerConfig,
|
|
21
|
+
public schema?: BrokerSerializer<T>
|
|
22
|
+
) {
|
|
23
|
+
this.schema = schema ?? getDefaultJsonSchema<T>();
|
|
24
|
+
const kafkaConfig: KafkaConfig = {
|
|
25
|
+
clientId: config.clientId ?? "kafka-client",
|
|
26
|
+
brokers: config.brokers,
|
|
27
|
+
retry: {
|
|
28
|
+
retries: config.retry?.retries ?? 1,
|
|
29
|
+
initialRetryTime: config.retry?.initialRetryTime ?? 300,
|
|
30
|
+
maxRetryTime: config.retry?.backoffMax ?? 1000,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
this.client = new Kafka(kafkaConfig);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Connects to the Kafka cluster.
|
|
39
|
+
* If the producer is already connected, this method returns immediately.
|
|
40
|
+
* Otherwise, it creates a new producer and connects to the Kafka cluster.
|
|
41
|
+
* @returns A promise resolved when the producer is connected.
|
|
42
|
+
*/
|
|
43
|
+
public async connect(): Promise<void> {
|
|
44
|
+
if (this.producer) return;
|
|
45
|
+
this.producer = this.client.producer({
|
|
46
|
+
idempotent: this.config.idempotent ?? true,
|
|
47
|
+
allowAutoTopicCreation: this.config.allowAutoTopicCreation ?? true,
|
|
48
|
+
});
|
|
49
|
+
await this.producer.connect();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Disconnects from the Kafka cluster.
|
|
54
|
+
* If the producer is not currently connected, this method does nothing.
|
|
55
|
+
* @returns A promise resolved when the producer is disconnected.
|
|
56
|
+
*/
|
|
57
|
+
public async disconnect(): Promise<void> {
|
|
58
|
+
if (this.producer) {
|
|
59
|
+
await this.producer.disconnect();
|
|
60
|
+
this.producer = undefined;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Sends a message to the Kafka cluster.
|
|
66
|
+
* If the producer is not currently connected, this method will first connect to the Kafka cluster.
|
|
67
|
+
* The message will be serialized using the provided schema.
|
|
68
|
+
* If the message key is provided, it will be used to determine the partition to send the message to.
|
|
69
|
+
* If the message headers are provided, they will be sent with the message.
|
|
70
|
+
* If the message partition is provided, the message will be sent to that partition.
|
|
71
|
+
* If the message timestamp is provided, it will be used as the timestamp for the message.
|
|
72
|
+
* In case of transient failures, the method will retry up to the configured number of retries.
|
|
73
|
+
* @param topic - The topic to send the message to.
|
|
74
|
+
* @param message - The message to send.
|
|
75
|
+
* @returns A promise resolved when the message has been sent to the topic.
|
|
76
|
+
*/
|
|
77
|
+
public async send(topic: string, message: Message<T>): Promise<void> {
|
|
78
|
+
if (!this.producer) await this.connect();
|
|
79
|
+
|
|
80
|
+
const valueBuf = this.schema.serialize(message.value);
|
|
81
|
+
const payload: ProducerRecord = {
|
|
82
|
+
topic,
|
|
83
|
+
messages: [
|
|
84
|
+
{
|
|
85
|
+
key: message.key
|
|
86
|
+
? typeof message.key === "string"
|
|
87
|
+
? message.key
|
|
88
|
+
: message.key.toString()
|
|
89
|
+
: undefined,
|
|
90
|
+
value: valueBuf,
|
|
91
|
+
headers: message.headers,
|
|
92
|
+
partition: message.partition,
|
|
93
|
+
timestamp: message.timestamp,
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Simple retry wrapper with backoff in case of transient failures
|
|
99
|
+
let attempt = 0;
|
|
100
|
+
while (true) {
|
|
101
|
+
try {
|
|
102
|
+
await this.producer!.send(payload);
|
|
103
|
+
return;
|
|
104
|
+
} catch (e) {
|
|
105
|
+
attempt++;
|
|
106
|
+
const max = this.config.retry?.retries ?? 1;
|
|
107
|
+
if (attempt > max) throw e;
|
|
108
|
+
const wait = backoff(attempt, 100, 3000);
|
|
109
|
+
await new Promise((r) => setTimeout(r, wait));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { BrokerSerializer } from "../utilities";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Supported RabbitMQ exchange types.
|
|
5
|
+
*/
|
|
6
|
+
export type ExchangeType = "direct" | "fanout" | "topic" | "headers";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Configuration options for a RabbitMQ producer.
|
|
10
|
+
*/
|
|
11
|
+
export interface ProducerConfig<T = any> {
|
|
12
|
+
url: string;
|
|
13
|
+
exchange?: string;
|
|
14
|
+
exchangeType?: ExchangeType;
|
|
15
|
+
routingKey?: string;
|
|
16
|
+
confirm?: boolean;
|
|
17
|
+
retry?: {
|
|
18
|
+
retries?: number;
|
|
19
|
+
baseMs?: number;
|
|
20
|
+
maxMs?: number;
|
|
21
|
+
};
|
|
22
|
+
serializer?: BrokerSerializer<T>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Configuration options for a RabbitMQ consumer.
|
|
27
|
+
*/
|
|
28
|
+
export interface ConsumerConfig<T = any> {
|
|
29
|
+
url: string;
|
|
30
|
+
queue: string;
|
|
31
|
+
exchange?: string;
|
|
32
|
+
exchangeType?: ExchangeType;
|
|
33
|
+
routingKey?: string;
|
|
34
|
+
prefetch?: number;
|
|
35
|
+
durable?: boolean;
|
|
36
|
+
onMessage: (msg: {
|
|
37
|
+
content: T;
|
|
38
|
+
fields: any;
|
|
39
|
+
properties: any;
|
|
40
|
+
}) => Promise<void> | void;
|
|
41
|
+
ackMode?: "auto" | "manual";
|
|
42
|
+
retry?: { retries?: number; baseMs?: number; maxMs?: number };
|
|
43
|
+
serializer?: BrokerSerializer<T>;
|
|
44
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import amqplib, { Connection, Channel } from "amqplib";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Manages a RabbitMQ channel and connection.
|
|
5
|
+
*/
|
|
6
|
+
export class ChannelManager {
|
|
7
|
+
private connection?: Connection;
|
|
8
|
+
private channel?: Channel;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creates a new ChannelManager.
|
|
12
|
+
* @param opts - Connection options.
|
|
13
|
+
* @param opts.url - The URL of the RabbitMQ server.
|
|
14
|
+
*/
|
|
15
|
+
public constructor(private opts: { url: string }) {}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Establishes a connection to the RabbitMQ server and creates a new channel.
|
|
19
|
+
* If a connection has already been established, this method returns immediately.
|
|
20
|
+
* @returns A promise resolved when the connection has been established.
|
|
21
|
+
*/
|
|
22
|
+
public async connect(): Promise<void> {
|
|
23
|
+
if (this.connection) return;
|
|
24
|
+
this.connection = await amqplib.connect(this.opts.url);
|
|
25
|
+
this.channel = await this.connection.createChannel();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Returns the underlying Channel object.
|
|
30
|
+
* Throws if connect() has not been called first.
|
|
31
|
+
* @returns The underlying Channel object.
|
|
32
|
+
*/
|
|
33
|
+
public get ch(): Channel {
|
|
34
|
+
if (!this.channel)
|
|
35
|
+
throw new Error("Channel not initialized. Call connect() first.");
|
|
36
|
+
return this.channel;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Closes the underlying Channel and Connection objects.
|
|
41
|
+
* If the Channel or Connection objects do not exist, this method does nothing.
|
|
42
|
+
* @returns A promise resolved when the Channel and Connection objects have been closed.
|
|
43
|
+
*/
|
|
44
|
+
public async close(): Promise<void> {
|
|
45
|
+
await this.channel?.close?.();
|
|
46
|
+
await this.connection?.close?.();
|
|
47
|
+
this.channel = undefined;
|
|
48
|
+
this.connection = undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Asserts the existence of a queue on the RabbitMQ server.
|
|
53
|
+
* @param queue - The name of the queue to assert.
|
|
54
|
+
* @param durable - Optional flag indicating whether the queue should be durable.
|
|
55
|
+
* @returns A promise resolved when the queue has been asserted.
|
|
56
|
+
* @throws If the queue does not exist or if the durable flag does not match the queue's durable status.
|
|
57
|
+
*/
|
|
58
|
+
public async assertQueue(
|
|
59
|
+
queue: string,
|
|
60
|
+
durable: boolean = true
|
|
61
|
+
): Promise<void> {
|
|
62
|
+
await this.ch.assertQueue(queue, { durable });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Asserts the existence of an exchange on the RabbitMQ server.
|
|
67
|
+
* @param name - The name of the exchange to assert.
|
|
68
|
+
* @param type - The type of the exchange to assert.
|
|
69
|
+
* @param durable - Optional flag indicating whether the exchange should be durable.
|
|
70
|
+
* @returns A promise resolved when the exchange has been asserted.
|
|
71
|
+
* @throws If the exchange does not exist or if the durable flag does not match the exchange's durable status.
|
|
72
|
+
*/
|
|
73
|
+
public async assertExchange(
|
|
74
|
+
name: string,
|
|
75
|
+
type: string,
|
|
76
|
+
durable: boolean = true
|
|
77
|
+
): Promise<void> {
|
|
78
|
+
await this.ch.assertExchange(name, type, { durable });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Binds a queue to an exchange with an optional routing key.
|
|
83
|
+
* @param queue - The name of the queue to bind.
|
|
84
|
+
* @param exchange - The name of the exchange to bind to.
|
|
85
|
+
* @param routingKey - Optional routing key to use when binding the queue to the exchange.
|
|
86
|
+
* @returns A promise resolved when the queue has been bound to the exchange.
|
|
87
|
+
*/
|
|
88
|
+
public async bindQueue(
|
|
89
|
+
queue: string,
|
|
90
|
+
exchange: string,
|
|
91
|
+
routingKey: string = ""
|
|
92
|
+
): Promise<void> {
|
|
93
|
+
await this.ch.bindQueue(queue, exchange, routingKey);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { ChannelManager } from "./channel";
|
|
2
|
+
import { ConsumerConfig } from "./basic-types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* RabbitMQ consumer.
|
|
6
|
+
* @template T - The type of the message body.
|
|
7
|
+
*/
|
|
8
|
+
export class RabbitConsumer<T> {
|
|
9
|
+
private cm = new ChannelManager({ url: "" });
|
|
10
|
+
private config: ConsumerConfig<T>;
|
|
11
|
+
private messageHandler?: (msg: {
|
|
12
|
+
content: T;
|
|
13
|
+
fields: any;
|
|
14
|
+
properties: any;
|
|
15
|
+
}) => Promise<void> | void;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates a new RabbitMQ consumer.
|
|
19
|
+
* @param opts - Configuration options for the consumer.
|
|
20
|
+
*/
|
|
21
|
+
public constructor(opts: ConsumerConfig<T>) {
|
|
22
|
+
this.config = opts;
|
|
23
|
+
this.cm = new ChannelManager({ url: opts.url });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Connects to the RabbitMQ cluster and sets up the consumer.
|
|
28
|
+
* Ensures the queue and exchange exist and binds the queue to the exchange.
|
|
29
|
+
* @returns A promise resolved when the consumer is connected.
|
|
30
|
+
*/
|
|
31
|
+
public async connect(): Promise<void> {
|
|
32
|
+
await this.cm.connect();
|
|
33
|
+
// ensure queue exists
|
|
34
|
+
await this.cm.assertQueue(this.config.queue, true);
|
|
35
|
+
if (this.config.exchange) {
|
|
36
|
+
const type = this.config.exchangeType ?? "direct";
|
|
37
|
+
// ensure exchange exists
|
|
38
|
+
await this.cm.assertExchange(this.config.exchange, type, true);
|
|
39
|
+
// bind queue
|
|
40
|
+
const routingKey = this.config.routingKey ?? "";
|
|
41
|
+
await this.cm.bindQueue(
|
|
42
|
+
this.config.queue,
|
|
43
|
+
this.config.exchange,
|
|
44
|
+
routingKey
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Starts the consumer and begins consuming messages from the subscribed queue.
|
|
51
|
+
* If the consumer is not already connected, it will first connect to the RabbitMQ cluster.
|
|
52
|
+
* @param prefetch - Optional prefetch count to set on the channel.
|
|
53
|
+
* If not specified, defaults to the value of `prefetch` in the consumer config.
|
|
54
|
+
* @returns A promise resolved when the consumer is started.
|
|
55
|
+
* @throws If the queue is not specified in the consumer config.
|
|
56
|
+
*/
|
|
57
|
+
public async start(prefetch = this.config.prefetch ?? 1): Promise<void> {
|
|
58
|
+
if (!this.config.queue) throw new Error("Queue must be specified");
|
|
59
|
+
await this.connect();
|
|
60
|
+
await this.cm.ch.prefetch(prefetch);
|
|
61
|
+
await this.cm.ch.consume(this.config.queue, async (msg) => {
|
|
62
|
+
if (!msg) return;
|
|
63
|
+
// Deserialize message body
|
|
64
|
+
let content: T;
|
|
65
|
+
if (this.config?.serializer?.deserialize) {
|
|
66
|
+
content = this.config.serializer.deserialize(msg.content) as T;
|
|
67
|
+
} else {
|
|
68
|
+
content = JSON.parse(msg.content.toString()) as T;
|
|
69
|
+
}
|
|
70
|
+
const payload = {
|
|
71
|
+
content,
|
|
72
|
+
fields: msg.fields,
|
|
73
|
+
properties: msg.properties,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
if (this.messageHandler) await this.messageHandler(payload);
|
|
77
|
+
|
|
78
|
+
// Acknowledge in either auto or manual mode
|
|
79
|
+
if (this.config.ackMode !== "manual") {
|
|
80
|
+
this.cm.ch.ack(msg);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Stops the consumer and closes the underlying Channel and Connection objects.
|
|
87
|
+
* The consumer will no longer receive messages from the subscribed queue.
|
|
88
|
+
* @returns A promise resolved when the consumer has been stopped.
|
|
89
|
+
*/
|
|
90
|
+
public async stop(): Promise<void> {
|
|
91
|
+
// Rudimentary stop: Cancel consumption by closing channel/connection
|
|
92
|
+
await this.cm.close();
|
|
93
|
+
}
|
|
94
|
+
}
|