@vercube/queue 1.3.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/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/QueueStrategy-DwLPpfFM.mjs +237 -0
- package/dist/QueueStrategy-saWSBeU6.d.mts +715 -0
- package/dist/Strategies/BullMQStrategy.d.mts +161 -0
- package/dist/Strategies/BullMQStrategy.mjs +328 -0
- package/dist/Strategies/KafkaStrategy.d.mts +152 -0
- package/dist/Strategies/KafkaStrategy.mjs +280 -0
- package/dist/Strategies/MemoryStrategy.d.mts +139 -0
- package/dist/Strategies/MemoryStrategy.mjs +305 -0
- package/dist/Strategies/RabbitMQStrategy.d.mts +205 -0
- package/dist/Strategies/RabbitMQStrategy.mjs +388 -0
- package/dist/decorate-C0p0FnUM.mjs +25 -0
- package/dist/index.d.mts +953 -0
- package/dist/index.mjs +1719 -0
- package/package.json +54 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { n as QueueTypes, t as QueueStrategy } from "../QueueStrategy-saWSBeU6.mjs";
|
|
2
|
+
import { ConnectionOptions, JobsOptions, QueueOptions, WorkerOptions } from "bullmq";
|
|
3
|
+
//#region src/Strategies/BullMQStrategy.d.ts
|
|
4
|
+
/** Options the BullMQ strategy connects with. */
|
|
5
|
+
export interface BullMQStrategyOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Redis connection, either as options or as an existing ioredis client.
|
|
8
|
+
* @see {@link https://docs.bullmq.io/guide/connections}
|
|
9
|
+
*/
|
|
10
|
+
connection: ConnectionOptions;
|
|
11
|
+
/** Key prefix every queue lives under in Redis. */
|
|
12
|
+
prefix?: string;
|
|
13
|
+
/** Job options applied to every published job, overridable per job. */
|
|
14
|
+
defaultJobOptions?: JobsOptions;
|
|
15
|
+
/** Extra options handed to every `Queue` this strategy creates. */
|
|
16
|
+
queueOptions?: Omit<QueueOptions, 'connection' | 'prefix' | 'defaultJobOptions'>;
|
|
17
|
+
/** Extra options handed to every `Worker` this strategy creates. */
|
|
18
|
+
workerOptions?: Omit<WorkerOptions, 'connection' | 'prefix' | 'concurrency'>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* BullMQ backed queue implementation.
|
|
22
|
+
*
|
|
23
|
+
* Redis keeps the jobs, and BullMQ owns their lifecycle: attempts, backoff,
|
|
24
|
+
* delays, priorities, progress and the completed and failed sets are all
|
|
25
|
+
* handled by the broker. Because retries belong to BullMQ, the attempts of a job
|
|
26
|
+
* are the ones it was published with - `attempts` on `@Job()` is only a fallback
|
|
27
|
+
* for jobs published without any.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* await queueManager.mount({
|
|
32
|
+
* strategy: BullMQStrategy,
|
|
33
|
+
* initOptions: { connection: { host: '127.0.0.1', port: 6379 } },
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare class BullMQStrategy extends QueueStrategy<BullMQStrategyOptions> {
|
|
38
|
+
/** Transport this strategy talks to. */
|
|
39
|
+
readonly transport: string;
|
|
40
|
+
/** Logger instance */
|
|
41
|
+
private gLogger;
|
|
42
|
+
/** Options the strategy was initialized with */
|
|
43
|
+
private fOptions;
|
|
44
|
+
/** Producers, one per queue name */
|
|
45
|
+
private fQueues;
|
|
46
|
+
/** Consumers, one per queue name */
|
|
47
|
+
private fWorkers;
|
|
48
|
+
/**
|
|
49
|
+
* BullMQ supports the full job model natively.
|
|
50
|
+
*
|
|
51
|
+
* @returns {QueueTypes.Capabilities} What this strategy supports
|
|
52
|
+
*/
|
|
53
|
+
get capabilities(): QueueTypes.Capabilities;
|
|
54
|
+
/**
|
|
55
|
+
* Stores the connection every queue and worker is created with.
|
|
56
|
+
* Redis itself is connected lazily by BullMQ, on the first command.
|
|
57
|
+
*
|
|
58
|
+
* @param {BullMQStrategyOptions} options - Redis connection and BullMQ defaults
|
|
59
|
+
* @returns {void}
|
|
60
|
+
* @throws {QueueError} When no connection is given
|
|
61
|
+
*/
|
|
62
|
+
initialize(options: BullMQStrategyOptions): void;
|
|
63
|
+
/**
|
|
64
|
+
* Adds a job to a BullMQ queue.
|
|
65
|
+
*
|
|
66
|
+
* @param {QueueTypes.PublishRequest} request - Job to publish
|
|
67
|
+
* @returns {Promise<QueueTypes.JobRef>} Reference to the published job
|
|
68
|
+
* @throws {QueueError} When the job cannot be added
|
|
69
|
+
*/
|
|
70
|
+
publish(request: QueueTypes.PublishRequest): Promise<QueueTypes.JobRef>;
|
|
71
|
+
/**
|
|
72
|
+
* Adds many jobs in a single Redis round trip.
|
|
73
|
+
*
|
|
74
|
+
* @param {QueueTypes.PublishRequest[]} requests - Jobs to publish, all on the same queue
|
|
75
|
+
* @returns {Promise<QueueTypes.JobRef[]>} References to the published jobs
|
|
76
|
+
* @throws {QueueError} When the jobs cannot be added
|
|
77
|
+
*/
|
|
78
|
+
publishMany(requests: QueueTypes.PublishRequest[]): Promise<QueueTypes.JobRef[]>;
|
|
79
|
+
/**
|
|
80
|
+
* Starts a BullMQ worker on a queue.
|
|
81
|
+
*
|
|
82
|
+
* A rejected dispatch is rethrown into BullMQ, which then applies the
|
|
83
|
+
* attempts and backoff the job was published with.
|
|
84
|
+
*
|
|
85
|
+
* @param {QueueTypes.ConsumeRequest} request - Queue to consume, its concurrency and the dispatch callback
|
|
86
|
+
* @returns {Promise<QueueTypes.ConsumerHandle>} Handle used to stop the worker again
|
|
87
|
+
* @throws {QueueError} When the worker cannot be started
|
|
88
|
+
*/
|
|
89
|
+
consume(request: QueueTypes.ConsumeRequest): Promise<QueueTypes.ConsumerHandle>;
|
|
90
|
+
/**
|
|
91
|
+
* Reads the job counts BullMQ keeps for a queue.
|
|
92
|
+
*
|
|
93
|
+
* @param {string} queue - Queue to read
|
|
94
|
+
* @returns {Promise<QueueTypes.QueueStats>} Counters of that queue
|
|
95
|
+
* @throws {QueueError} When the counters cannot be read
|
|
96
|
+
*/
|
|
97
|
+
stats(queue: string): Promise<QueueTypes.QueueStats>;
|
|
98
|
+
/**
|
|
99
|
+
* Shows what a queue is holding, straight from Redis and without consuming
|
|
100
|
+
* anything. The failed set is the interesting one: BullMQ keeps the data and
|
|
101
|
+
* the stack trace of every job that ran out of attempts.
|
|
102
|
+
*
|
|
103
|
+
* @param {QueueTypes.PeekRequest} request - Queue to look at, how many messages and which states
|
|
104
|
+
* @returns {Promise<QueueTypes.PeekedMessage[]>} The messages found
|
|
105
|
+
* @throws {QueueError} When the queue cannot be read
|
|
106
|
+
*/
|
|
107
|
+
peek(request: QueueTypes.PeekRequest): Promise<QueueTypes.PeekedMessage[]>;
|
|
108
|
+
/**
|
|
109
|
+
* Closes every worker and producer this strategy created.
|
|
110
|
+
*
|
|
111
|
+
* @returns {Promise<void>} Resolves once everything is closed
|
|
112
|
+
*/
|
|
113
|
+
close(): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* Describes a BullMQ job the way the module reads a peeked message.
|
|
116
|
+
*
|
|
117
|
+
* @param {Job} job - The job as Redis holds it
|
|
118
|
+
* @param {QueueTypes.PeekState} state - Set it was read from
|
|
119
|
+
* @returns {QueueTypes.PeekedMessage} The message, ready to be inspected
|
|
120
|
+
*/
|
|
121
|
+
private describe;
|
|
122
|
+
/**
|
|
123
|
+
* Returns the producer of a queue, creating it on first use.
|
|
124
|
+
*
|
|
125
|
+
* @param {string} name - Queue name
|
|
126
|
+
* @returns {Queue} The BullMQ queue
|
|
127
|
+
* @throws {QueueError} When the strategy has not been initialized
|
|
128
|
+
*/
|
|
129
|
+
private queueFor;
|
|
130
|
+
/**
|
|
131
|
+
* Translates the module's job options into BullMQ job options.
|
|
132
|
+
*
|
|
133
|
+
* @param {QueueTypes.JobOptions} options - Options of the job being published
|
|
134
|
+
* @returns {JobsOptions} The BullMQ options
|
|
135
|
+
*/
|
|
136
|
+
private toJobOptions;
|
|
137
|
+
/**
|
|
138
|
+
* Wraps a job so its headers survive the round trip through Redis.
|
|
139
|
+
*
|
|
140
|
+
* @param {QueueTypes.PublishRequest} request - Job being published
|
|
141
|
+
* @returns {BullMQEnvelope} What is stored as the BullMQ job data
|
|
142
|
+
*/
|
|
143
|
+
private toEnvelope;
|
|
144
|
+
/**
|
|
145
|
+
* Reads a job back. Data that is not one of this module's envelopes is treated
|
|
146
|
+
* as the payload itself, so jobs added by other BullMQ producers still work.
|
|
147
|
+
*
|
|
148
|
+
* @param {unknown} data - Data as stored in Redis
|
|
149
|
+
* @returns {BullMQEnvelope} Payload and headers of the job
|
|
150
|
+
*/
|
|
151
|
+
private fromEnvelope;
|
|
152
|
+
/**
|
|
153
|
+
* Returns the options the strategy was initialized with.
|
|
154
|
+
*
|
|
155
|
+
* @param {string} operation - Operation asking for them, reported in the error
|
|
156
|
+
* @returns {BullMQStrategyOptions} The options
|
|
157
|
+
* @throws {QueueError} When the strategy has not been initialized
|
|
158
|
+
*/
|
|
159
|
+
private requireOptions;
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { h as prune, t as QueueStrategy, v as QueueError } from "../QueueStrategy-DwLPpfFM.mjs";
|
|
2
|
+
import { n as toQueueError, t as __decorate } from "../decorate-C0p0FnUM.mjs";
|
|
3
|
+
import { InjectOptional } from "@vercube/di";
|
|
4
|
+
import { Logger } from "@vercube/logger";
|
|
5
|
+
import { Queue, UnrecoverableError, Worker } from "bullmq";
|
|
6
|
+
//#region src/Strategies/BullMQStrategy.ts
|
|
7
|
+
/**
|
|
8
|
+
* BullMQ backed queue implementation.
|
|
9
|
+
*
|
|
10
|
+
* Redis keeps the jobs, and BullMQ owns their lifecycle: attempts, backoff,
|
|
11
|
+
* delays, priorities, progress and the completed and failed sets are all
|
|
12
|
+
* handled by the broker. Because retries belong to BullMQ, the attempts of a job
|
|
13
|
+
* are the ones it was published with - `attempts` on `@Job()` is only a fallback
|
|
14
|
+
* for jobs published without any.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* await queueManager.mount({
|
|
19
|
+
* strategy: BullMQStrategy,
|
|
20
|
+
* initOptions: { connection: { host: '127.0.0.1', port: 6379 } },
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
var BullMQStrategy = class extends QueueStrategy {
|
|
25
|
+
/** Transport this strategy talks to. */
|
|
26
|
+
transport = "bullmq";
|
|
27
|
+
/** Logger instance */
|
|
28
|
+
gLogger;
|
|
29
|
+
/** Options the strategy was initialized with */
|
|
30
|
+
fOptions = null;
|
|
31
|
+
/** Producers, one per queue name */
|
|
32
|
+
fQueues = /* @__PURE__ */ new Map();
|
|
33
|
+
/** Consumers, one per queue name */
|
|
34
|
+
fWorkers = /* @__PURE__ */ new Map();
|
|
35
|
+
/**
|
|
36
|
+
* BullMQ supports the full job model natively.
|
|
37
|
+
*
|
|
38
|
+
* @returns {QueueTypes.Capabilities} What this strategy supports
|
|
39
|
+
*/
|
|
40
|
+
get capabilities() {
|
|
41
|
+
return {
|
|
42
|
+
retries: true,
|
|
43
|
+
delay: true,
|
|
44
|
+
priority: true,
|
|
45
|
+
progress: true,
|
|
46
|
+
stats: true,
|
|
47
|
+
peek: true
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Stores the connection every queue and worker is created with.
|
|
52
|
+
* Redis itself is connected lazily by BullMQ, on the first command.
|
|
53
|
+
*
|
|
54
|
+
* @param {BullMQStrategyOptions} options - Redis connection and BullMQ defaults
|
|
55
|
+
* @returns {void}
|
|
56
|
+
* @throws {QueueError} When no connection is given
|
|
57
|
+
*/
|
|
58
|
+
initialize(options) {
|
|
59
|
+
if (!options?.connection) throw new QueueError("BullMQ needs a Redis connection", "initialize", void 0, void 0, false);
|
|
60
|
+
this.fOptions = options;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Adds a job to a BullMQ queue.
|
|
64
|
+
*
|
|
65
|
+
* @param {QueueTypes.PublishRequest} request - Job to publish
|
|
66
|
+
* @returns {Promise<QueueTypes.JobRef>} Reference to the published job
|
|
67
|
+
* @throws {QueueError} When the job cannot be added
|
|
68
|
+
*/
|
|
69
|
+
async publish(request) {
|
|
70
|
+
try {
|
|
71
|
+
const job = await this.queueFor(request.queue).add(request.job, this.toEnvelope(request), this.toJobOptions(request.options));
|
|
72
|
+
return {
|
|
73
|
+
id: String(job.id),
|
|
74
|
+
queue: request.queue,
|
|
75
|
+
job: request.job,
|
|
76
|
+
strategy: this.transport
|
|
77
|
+
};
|
|
78
|
+
} catch (error) {
|
|
79
|
+
throw toQueueError(error, "Failed to add job to BullMQ", "publish", {
|
|
80
|
+
queue: request.queue,
|
|
81
|
+
job: request.job
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Adds many jobs in a single Redis round trip.
|
|
87
|
+
*
|
|
88
|
+
* @param {QueueTypes.PublishRequest[]} requests - Jobs to publish, all on the same queue
|
|
89
|
+
* @returns {Promise<QueueTypes.JobRef[]>} References to the published jobs
|
|
90
|
+
* @throws {QueueError} When the jobs cannot be added
|
|
91
|
+
*/
|
|
92
|
+
async publishMany(requests) {
|
|
93
|
+
if (requests.length === 0) return [];
|
|
94
|
+
try {
|
|
95
|
+
return (await this.queueFor(requests[0].queue).addBulk(requests.map((request) => ({
|
|
96
|
+
name: request.job,
|
|
97
|
+
data: this.toEnvelope(request),
|
|
98
|
+
opts: this.toJobOptions(request.options)
|
|
99
|
+
})))).map((job, index) => ({
|
|
100
|
+
id: String(job.id),
|
|
101
|
+
queue: requests[index].queue,
|
|
102
|
+
job: requests[index].job,
|
|
103
|
+
strategy: this.transport
|
|
104
|
+
}));
|
|
105
|
+
} catch (error) {
|
|
106
|
+
throw toQueueError(error, "Failed to add jobs to BullMQ", "publishMany", { queue: requests[0].queue });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Starts a BullMQ worker on a queue.
|
|
111
|
+
*
|
|
112
|
+
* A rejected dispatch is rethrown into BullMQ, which then applies the
|
|
113
|
+
* attempts and backoff the job was published with.
|
|
114
|
+
*
|
|
115
|
+
* @param {QueueTypes.ConsumeRequest} request - Queue to consume, its concurrency and the dispatch callback
|
|
116
|
+
* @returns {Promise<QueueTypes.ConsumerHandle>} Handle used to stop the worker again
|
|
117
|
+
* @throws {QueueError} When the worker cannot be started
|
|
118
|
+
*/
|
|
119
|
+
async consume(request) {
|
|
120
|
+
const options = this.requireOptions("consume");
|
|
121
|
+
const existing = this.fWorkers.get(request.queue);
|
|
122
|
+
if (existing) await existing.close();
|
|
123
|
+
const worker = new Worker(request.queue, async (job) => {
|
|
124
|
+
const { payload, headers } = this.fromEnvelope(job.data);
|
|
125
|
+
try {
|
|
126
|
+
await request.dispatch({
|
|
127
|
+
id: String(job.id),
|
|
128
|
+
job: job.name,
|
|
129
|
+
payload,
|
|
130
|
+
headers,
|
|
131
|
+
attempt: job.attemptsStarted || job.attemptsMade + 1,
|
|
132
|
+
attempts: job.opts?.attempts ?? 1,
|
|
133
|
+
raw: job,
|
|
134
|
+
updateProgress: (progress) => job.updateProgress(progress)
|
|
135
|
+
});
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (error instanceof QueueError && !error.retryable) {
|
|
138
|
+
const cause = error.cause instanceof Error ? `: ${error.cause.message}` : "";
|
|
139
|
+
throw new UnrecoverableError(`${error.message}${cause}`);
|
|
140
|
+
}
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}, {
|
|
144
|
+
...options.workerOptions,
|
|
145
|
+
connection: options.connection,
|
|
146
|
+
prefix: options.prefix,
|
|
147
|
+
concurrency: request.concurrency
|
|
148
|
+
});
|
|
149
|
+
worker.on("error", (error) => {
|
|
150
|
+
this.gLogger?.error(`Vercube/BullMQStrategy::Worker of "${request.queue}" failed`, error);
|
|
151
|
+
});
|
|
152
|
+
this.fWorkers.set(request.queue, worker);
|
|
153
|
+
return {
|
|
154
|
+
queue: request.queue,
|
|
155
|
+
stop: async () => {
|
|
156
|
+
this.fWorkers.delete(request.queue);
|
|
157
|
+
await worker.close();
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Reads the job counts BullMQ keeps for a queue.
|
|
163
|
+
*
|
|
164
|
+
* @param {string} queue - Queue to read
|
|
165
|
+
* @returns {Promise<QueueTypes.QueueStats>} Counters of that queue
|
|
166
|
+
* @throws {QueueError} When the counters cannot be read
|
|
167
|
+
*/
|
|
168
|
+
async stats(queue) {
|
|
169
|
+
try {
|
|
170
|
+
const counts = await this.queueFor(queue).getJobCounts("waiting", "active", "completed", "failed", "delayed");
|
|
171
|
+
return {
|
|
172
|
+
waiting: counts.waiting,
|
|
173
|
+
active: counts.active,
|
|
174
|
+
completed: counts.completed,
|
|
175
|
+
failed: counts.failed,
|
|
176
|
+
delayed: counts.delayed
|
|
177
|
+
};
|
|
178
|
+
} catch (error) {
|
|
179
|
+
throw toQueueError(error, "Failed to read BullMQ job counts", "stats", { queue });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Shows what a queue is holding, straight from Redis and without consuming
|
|
184
|
+
* anything. The failed set is the interesting one: BullMQ keeps the data and
|
|
185
|
+
* the stack trace of every job that ran out of attempts.
|
|
186
|
+
*
|
|
187
|
+
* @param {QueueTypes.PeekRequest} request - Queue to look at, how many messages and which states
|
|
188
|
+
* @returns {Promise<QueueTypes.PeekedMessage[]>} The messages found
|
|
189
|
+
* @throws {QueueError} When the queue cannot be read
|
|
190
|
+
*/
|
|
191
|
+
async peek(request) {
|
|
192
|
+
const queue = this.queueFor(request.queue);
|
|
193
|
+
const messages = [];
|
|
194
|
+
try {
|
|
195
|
+
for (const state of request.states) {
|
|
196
|
+
if (messages.length >= request.limit) break;
|
|
197
|
+
const jobs = await queue.getJobs([state], 0, request.limit - messages.length - 1);
|
|
198
|
+
for (const job of jobs) messages.push(this.describe(job, state));
|
|
199
|
+
}
|
|
200
|
+
} catch (error) {
|
|
201
|
+
throw toQueueError(error, "Failed to read the BullMQ queue", "peek", { queue: request.queue });
|
|
202
|
+
}
|
|
203
|
+
return messages;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Closes every worker and producer this strategy created.
|
|
207
|
+
*
|
|
208
|
+
* @returns {Promise<void>} Resolves once everything is closed
|
|
209
|
+
*/
|
|
210
|
+
async close() {
|
|
211
|
+
const closing = [...[...this.fWorkers.values()].map((worker) => worker.close()), ...[...this.fQueues.values()].map((queue) => queue.close())];
|
|
212
|
+
this.fWorkers.clear();
|
|
213
|
+
this.fQueues.clear();
|
|
214
|
+
await Promise.all(closing);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Describes a BullMQ job the way the module reads a peeked message.
|
|
218
|
+
*
|
|
219
|
+
* @param {Job} job - The job as Redis holds it
|
|
220
|
+
* @param {QueueTypes.PeekState} state - Set it was read from
|
|
221
|
+
* @returns {QueueTypes.PeekedMessage} The message, ready to be inspected
|
|
222
|
+
*/
|
|
223
|
+
describe(job, state) {
|
|
224
|
+
const { payload, headers } = this.fromEnvelope(job.data);
|
|
225
|
+
const delay = job.opts?.delay ?? 0;
|
|
226
|
+
return {
|
|
227
|
+
id: String(job.id),
|
|
228
|
+
job: job.name,
|
|
229
|
+
state,
|
|
230
|
+
attempt: job.attemptsStarted || job.attemptsMade || void 0,
|
|
231
|
+
payload,
|
|
232
|
+
headers,
|
|
233
|
+
availableAt: state === "delayed" && job.timestamp ? job.timestamp + delay : void 0,
|
|
234
|
+
error: job.failedReason ? {
|
|
235
|
+
message: job.failedReason,
|
|
236
|
+
stack: job.stacktrace?.join("\n") || void 0
|
|
237
|
+
} : void 0
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Returns the producer of a queue, creating it on first use.
|
|
242
|
+
*
|
|
243
|
+
* @param {string} name - Queue name
|
|
244
|
+
* @returns {Queue} The BullMQ queue
|
|
245
|
+
* @throws {QueueError} When the strategy has not been initialized
|
|
246
|
+
*/
|
|
247
|
+
queueFor(name) {
|
|
248
|
+
const options = this.requireOptions("publish");
|
|
249
|
+
let queue = this.fQueues.get(name);
|
|
250
|
+
if (!queue) {
|
|
251
|
+
queue = new Queue(name, {
|
|
252
|
+
...options.queueOptions,
|
|
253
|
+
connection: options.connection,
|
|
254
|
+
prefix: options.prefix,
|
|
255
|
+
defaultJobOptions: options.defaultJobOptions
|
|
256
|
+
});
|
|
257
|
+
this.fQueues.set(name, queue);
|
|
258
|
+
}
|
|
259
|
+
return queue;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Translates the module's job options into BullMQ job options.
|
|
263
|
+
*
|
|
264
|
+
* @param {QueueTypes.JobOptions} options - Options of the job being published
|
|
265
|
+
* @returns {JobsOptions} The BullMQ options
|
|
266
|
+
*/
|
|
267
|
+
toJobOptions(options) {
|
|
268
|
+
const backoff = options.backoff;
|
|
269
|
+
return prune({
|
|
270
|
+
attempts: options.attempts,
|
|
271
|
+
backoff: typeof backoff === "number" ? {
|
|
272
|
+
type: "fixed",
|
|
273
|
+
delay: backoff
|
|
274
|
+
} : backoff,
|
|
275
|
+
delay: options.delay,
|
|
276
|
+
priority: options.priority,
|
|
277
|
+
jobId: options.jobId,
|
|
278
|
+
removeOnComplete: options.removeOnComplete,
|
|
279
|
+
removeOnFail: options.removeOnFail
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Wraps a job so its headers survive the round trip through Redis.
|
|
284
|
+
*
|
|
285
|
+
* @param {QueueTypes.PublishRequest} request - Job being published
|
|
286
|
+
* @returns {BullMQEnvelope} What is stored as the BullMQ job data
|
|
287
|
+
*/
|
|
288
|
+
toEnvelope(request) {
|
|
289
|
+
return {
|
|
290
|
+
payload: request.payload,
|
|
291
|
+
headers: request.headers
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Reads a job back. Data that is not one of this module's envelopes is treated
|
|
296
|
+
* as the payload itself, so jobs added by other BullMQ producers still work.
|
|
297
|
+
*
|
|
298
|
+
* @param {unknown} data - Data as stored in Redis
|
|
299
|
+
* @returns {BullMQEnvelope} Payload and headers of the job
|
|
300
|
+
*/
|
|
301
|
+
fromEnvelope(data) {
|
|
302
|
+
if (data !== null && typeof data === "object" && "payload" in data && "headers" in data) {
|
|
303
|
+
const envelope = data;
|
|
304
|
+
return {
|
|
305
|
+
payload: envelope.payload,
|
|
306
|
+
headers: envelope.headers ?? {}
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
payload: data,
|
|
311
|
+
headers: {}
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Returns the options the strategy was initialized with.
|
|
316
|
+
*
|
|
317
|
+
* @param {string} operation - Operation asking for them, reported in the error
|
|
318
|
+
* @returns {BullMQStrategyOptions} The options
|
|
319
|
+
* @throws {QueueError} When the strategy has not been initialized
|
|
320
|
+
*/
|
|
321
|
+
requireOptions(operation) {
|
|
322
|
+
if (!this.fOptions) throw new QueueError("BullMQ strategy is not initialized", operation, void 0, void 0, false);
|
|
323
|
+
return this.fOptions;
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
__decorate([InjectOptional(Logger)], BullMQStrategy.prototype, "gLogger", void 0);
|
|
327
|
+
//#endregion
|
|
328
|
+
export { BullMQStrategy };
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { n as QueueTypes, t as QueueStrategy } from "../QueueStrategy-saWSBeU6.mjs";
|
|
2
|
+
import { ConsumerConfig, KafkaConfig, ProducerConfig, ProducerRecord } from "kafkajs";
|
|
3
|
+
//#region src/Strategies/KafkaStrategy.d.ts
|
|
4
|
+
/** Options the Kafka strategy connects with. */
|
|
5
|
+
export interface KafkaStrategyOptions {
|
|
6
|
+
/** Client configuration: brokers, client id, ssl, sasl and the rest. */
|
|
7
|
+
client: KafkaConfig;
|
|
8
|
+
/** Consumer group every consumer of this strategy joins. Required to consume. */
|
|
9
|
+
groupId?: string;
|
|
10
|
+
/** Extra producer configuration. */
|
|
11
|
+
producer?: ProducerConfig;
|
|
12
|
+
/** Extra consumer configuration, the group id excluded. */
|
|
13
|
+
consumer?: Omit<ConsumerConfig, 'groupId'>;
|
|
14
|
+
/** Send options applied to every produced record. */
|
|
15
|
+
send?: Omit<ProducerRecord, 'topic' | 'messages'>;
|
|
16
|
+
/**
|
|
17
|
+
* Start from the earliest offset when the group has none committed yet.
|
|
18
|
+
* @default false
|
|
19
|
+
*/
|
|
20
|
+
fromBeginning?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* What to do with a job the manager gave up on. `skip` logs it and commits the
|
|
23
|
+
* offset, so the partition keeps moving, while `crash` lets the error reach
|
|
24
|
+
* kafkajs, which stops the consumer.
|
|
25
|
+
* @default 'skip'
|
|
26
|
+
*/
|
|
27
|
+
onFailure?: 'skip' | 'crash';
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Kafka backed queue implementation.
|
|
31
|
+
*
|
|
32
|
+
* A queue is a topic, the job name travels in the `x-job` header and the payload
|
|
33
|
+
* is JSON. Kafka is a log rather than a job broker: it has no attempts, delays,
|
|
34
|
+
* priorities or per-message acknowledgements, so those are handled by the manager
|
|
35
|
+
* and a job it gives up on only moves the offset forward.
|
|
36
|
+
*
|
|
37
|
+
* Ordering is per partition, so use the `key` job option to keep related jobs on
|
|
38
|
+
* the same partition.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* await queueManager.mount({
|
|
43
|
+
* strategy: KafkaStrategy,
|
|
44
|
+
* initOptions: {
|
|
45
|
+
* client: { clientId: 'orders', brokers: ['localhost:9092'] },
|
|
46
|
+
* groupId: 'orders-workers',
|
|
47
|
+
* },
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare class KafkaStrategy extends QueueStrategy<KafkaStrategyOptions> {
|
|
52
|
+
/** Transport this strategy talks to. */
|
|
53
|
+
readonly transport: string;
|
|
54
|
+
/** Logger instance */
|
|
55
|
+
private gLogger;
|
|
56
|
+
/** Options the strategy was initialized with */
|
|
57
|
+
private fOptions;
|
|
58
|
+
/** The Kafka client */
|
|
59
|
+
private fKafka;
|
|
60
|
+
/** The connected producer */
|
|
61
|
+
private fProducer;
|
|
62
|
+
/** Admin client, opened only when counters are read */
|
|
63
|
+
private fAdmin;
|
|
64
|
+
/** Running consumers, indexed by topic */
|
|
65
|
+
private fConsumers;
|
|
66
|
+
/**
|
|
67
|
+
* Kafka is a log: only the offsets it keeps can be reported, everything else
|
|
68
|
+
* about the job model is left to the manager.
|
|
69
|
+
*
|
|
70
|
+
* @returns {QueueTypes.Capabilities} What this strategy supports
|
|
71
|
+
*/
|
|
72
|
+
get capabilities(): QueueTypes.Capabilities;
|
|
73
|
+
/**
|
|
74
|
+
* Creates the client and connects the producer, so a broken configuration is
|
|
75
|
+
* reported at boot instead of on the first job.
|
|
76
|
+
*
|
|
77
|
+
* @param {KafkaStrategyOptions} options - Client configuration and consumer group
|
|
78
|
+
* @returns {Promise<void>} Resolves once the producer is connected
|
|
79
|
+
* @throws {QueueError} When no brokers are given, or the producer cannot connect
|
|
80
|
+
*/
|
|
81
|
+
initialize(options: KafkaStrategyOptions): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Produces a single record.
|
|
84
|
+
*
|
|
85
|
+
* @param {QueueTypes.PublishRequest} request - Job to publish
|
|
86
|
+
* @returns {Promise<QueueTypes.JobRef>} Reference to the produced record
|
|
87
|
+
* @throws {QueueError} When the record cannot be produced
|
|
88
|
+
*/
|
|
89
|
+
publish(request: QueueTypes.PublishRequest): Promise<QueueTypes.JobRef>;
|
|
90
|
+
/**
|
|
91
|
+
* Produces many records of the same topic in a single request.
|
|
92
|
+
*
|
|
93
|
+
* @param {QueueTypes.PublishRequest[]} requests - Jobs to publish, all on the same topic
|
|
94
|
+
* @returns {Promise<QueueTypes.JobRef[]>} References to the produced records
|
|
95
|
+
* @throws {QueueError} When the records cannot be produced
|
|
96
|
+
*/
|
|
97
|
+
publishMany(requests: QueueTypes.PublishRequest[]): Promise<QueueTypes.JobRef[]>;
|
|
98
|
+
/**
|
|
99
|
+
* Subscribes a consumer of the configured group to a topic.
|
|
100
|
+
*
|
|
101
|
+
* @param {QueueTypes.ConsumeRequest} request - Topic to consume, its concurrency and the dispatch callback
|
|
102
|
+
* @returns {Promise<QueueTypes.ConsumerHandle>} Handle used to stop the consumer again
|
|
103
|
+
* @throws {QueueError} When no consumer group is configured, or the consumer cannot start
|
|
104
|
+
*/
|
|
105
|
+
consume(request: QueueTypes.ConsumeRequest): Promise<QueueTypes.ConsumerHandle>;
|
|
106
|
+
/**
|
|
107
|
+
* Reports how far the consumer group is behind the end of the topic.
|
|
108
|
+
*
|
|
109
|
+
* @param {string} queue - Topic to read
|
|
110
|
+
* @returns {Promise<QueueTypes.QueueStats>} How many records are still to be read
|
|
111
|
+
* @throws {QueueError} When the offsets cannot be read
|
|
112
|
+
*/
|
|
113
|
+
stats(queue: string): Promise<QueueTypes.QueueStats>;
|
|
114
|
+
/**
|
|
115
|
+
* Disconnects every consumer, the producer and the admin client.
|
|
116
|
+
*
|
|
117
|
+
* @returns {Promise<void>} Resolves once everything is disconnected
|
|
118
|
+
*/
|
|
119
|
+
close(): Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
* Returns the admin client, connecting it on first use.
|
|
122
|
+
*
|
|
123
|
+
* @returns {Promise<Admin>} The connected admin client
|
|
124
|
+
* @throws {QueueError} When the strategy has not been initialized
|
|
125
|
+
*/
|
|
126
|
+
private adminClient;
|
|
127
|
+
/**
|
|
128
|
+
* Returns the options the strategy was initialized with.
|
|
129
|
+
*
|
|
130
|
+
* @param {string} operation - Operation asking for them, reported in the error
|
|
131
|
+
* @returns {KafkaStrategyOptions} The options
|
|
132
|
+
* @throws {QueueError} When the strategy has not been initialized
|
|
133
|
+
*/
|
|
134
|
+
private requireOptions;
|
|
135
|
+
/**
|
|
136
|
+
* Returns the Kafka client.
|
|
137
|
+
*
|
|
138
|
+
* @param {string} operation - Operation asking for it, reported in the error
|
|
139
|
+
* @returns {Kafka} The client
|
|
140
|
+
* @throws {QueueError} When the strategy has not been initialized
|
|
141
|
+
*/
|
|
142
|
+
private requireClient;
|
|
143
|
+
/**
|
|
144
|
+
* Returns the connected producer.
|
|
145
|
+
*
|
|
146
|
+
* @param {string} operation - Operation asking for it, reported in the error
|
|
147
|
+
* @returns {Producer} The producer
|
|
148
|
+
* @throws {QueueError} When the strategy has not been initialized
|
|
149
|
+
*/
|
|
150
|
+
private requireProducer;
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|