@pikku/queue-bullmq 0.9.4 → 0.9.5-next.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/CHANGELOG.md +7 -0
- package/dist/bull-queue-service.d.ts +27 -0
- package/dist/bull-queue-service.js +83 -0
- package/dist/bull-queue-worker.d.ts +31 -0
- package/dist/bull-queue-worker.js +155 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +8 -0
- package/dist/utils.d.ts +6 -0
- package/dist/utils.js +54 -0
- package/package.json +2 -2
- package/tsconfig.tsbuildinfo +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import Bull, { ConnectionOptions, JobsOptions } from 'bullmq';
|
|
2
|
+
import type { QueueService, QueueJob, JobOptions } from '@pikku/core/queue';
|
|
3
|
+
export declare const mapPikkuJobToBull: (options?: JobOptions) => JobsOptions;
|
|
4
|
+
/**
|
|
5
|
+
* Bull/Redis queue service implementation
|
|
6
|
+
* Supports job results, retries, and most queue features
|
|
7
|
+
*/
|
|
8
|
+
export declare class BullQueueService implements QueueService {
|
|
9
|
+
private redisConnectionOptions;
|
|
10
|
+
readonly name = "bull";
|
|
11
|
+
readonly supportsResults = true;
|
|
12
|
+
protected queues: Map<string, Bull.Queue<any, any, string, any, any, string>>;
|
|
13
|
+
protected queueEvents: Map<string, Bull.QueueEvents>;
|
|
14
|
+
constructor(redisConnectionOptions?: ConnectionOptions);
|
|
15
|
+
/**
|
|
16
|
+
* Create a Bull queue with translated configuration
|
|
17
|
+
*/
|
|
18
|
+
protected createQueue(queueName: string, config?: Bull.QueueOptions): Promise<Bull.Queue>;
|
|
19
|
+
/**
|
|
20
|
+
* Add a job to the queue
|
|
21
|
+
*/
|
|
22
|
+
add<In>(queueName: string, data: In, options?: JobOptions): Promise<string>;
|
|
23
|
+
/**
|
|
24
|
+
* Get job by ID
|
|
25
|
+
*/
|
|
26
|
+
getJob<T, R>(queueName: string, jobId: string): Promise<QueueJob<T, R> | null>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Queue, RedisConnection, } from 'bullmq';
|
|
2
|
+
import { mapBullJobToQueueJob } from './utils.js';
|
|
3
|
+
export const mapPikkuJobToBull = (options) => {
|
|
4
|
+
const bullOptions = {};
|
|
5
|
+
if (options?.priority !== undefined) {
|
|
6
|
+
bullOptions.priority = options.priority;
|
|
7
|
+
}
|
|
8
|
+
if (options?.delay !== undefined) {
|
|
9
|
+
bullOptions.delay = options.delay;
|
|
10
|
+
}
|
|
11
|
+
if (options?.attempts !== undefined) {
|
|
12
|
+
bullOptions.attempts = options.attempts;
|
|
13
|
+
}
|
|
14
|
+
if (options?.removeOnComplete !== undefined) {
|
|
15
|
+
bullOptions.removeOnComplete = options.removeOnComplete;
|
|
16
|
+
}
|
|
17
|
+
if (options?.removeOnFail !== undefined) {
|
|
18
|
+
bullOptions.removeOnFail = options.removeOnFail;
|
|
19
|
+
}
|
|
20
|
+
if (options?.jobId !== undefined) {
|
|
21
|
+
bullOptions.jobId = options.jobId;
|
|
22
|
+
}
|
|
23
|
+
return bullOptions;
|
|
24
|
+
};
|
|
25
|
+
class PikkuRedisConnection extends RedisConnection {
|
|
26
|
+
constructor(options) {
|
|
27
|
+
super(options);
|
|
28
|
+
this.on('error', (error) => {
|
|
29
|
+
console.error('Redis connection error:', error);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Bull/Redis queue service implementation
|
|
35
|
+
* Supports job results, retries, and most queue features
|
|
36
|
+
*/
|
|
37
|
+
export class BullQueueService {
|
|
38
|
+
redisConnectionOptions;
|
|
39
|
+
name = 'bull';
|
|
40
|
+
supportsResults = true;
|
|
41
|
+
queues = new Map();
|
|
42
|
+
queueEvents = new Map();
|
|
43
|
+
constructor(redisConnectionOptions = {}) {
|
|
44
|
+
this.redisConnectionOptions = redisConnectionOptions;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Create a Bull queue with translated configuration
|
|
48
|
+
*/
|
|
49
|
+
async createQueue(queueName, config) {
|
|
50
|
+
if (this.queues.has(queueName)) {
|
|
51
|
+
return this.queues.get(queueName);
|
|
52
|
+
}
|
|
53
|
+
const queue = new Queue(queueName, config, PikkuRedisConnection);
|
|
54
|
+
await queue.waitUntilReady();
|
|
55
|
+
this.queues.set(queueName, queue);
|
|
56
|
+
return queue;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Add a job to the queue
|
|
60
|
+
*/
|
|
61
|
+
async add(queueName, data, options) {
|
|
62
|
+
const queue = await this.createQueue(queueName);
|
|
63
|
+
const bullJob = await queue.add(queueName, data, mapPikkuJobToBull(options));
|
|
64
|
+
if (!bullJob.id) {
|
|
65
|
+
throw new Error(`Failed to add job to queue '${queueName}'.`);
|
|
66
|
+
}
|
|
67
|
+
return bullJob.id;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Get job by ID
|
|
71
|
+
*/
|
|
72
|
+
async getJob(queueName, jobId) {
|
|
73
|
+
const queue = this.queues.get(queueName);
|
|
74
|
+
if (!queue) {
|
|
75
|
+
throw new Error(`Queue '${queueName}' not found.`);
|
|
76
|
+
}
|
|
77
|
+
const bullJob = await queue.getJob(jobId);
|
|
78
|
+
if (!bullJob) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
return mapBullJobToQueueJob(bullJob, this.redisConnectionOptions, this.queueEvents);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ConnectionOptions, WorkerOptions } from 'bullmq';
|
|
2
|
+
import type { QueueWorkers, PikkuWorkerConfig, QueueConfigMapping, ConfigValidationResult } from '@pikku/core/queue';
|
|
3
|
+
import { CoreServices, CoreSingletonServices, CreateSessionServices } from '@pikku/core';
|
|
4
|
+
export declare const mapPikkuWorkerToBull: (workerConfig?: PikkuWorkerConfig) => Omit<WorkerOptions, "connection">;
|
|
5
|
+
/**
|
|
6
|
+
* Bull/Redis queue service implementation
|
|
7
|
+
* Supports job results, retries, and most queue features
|
|
8
|
+
*/
|
|
9
|
+
export declare class BullQueueWorkers implements QueueWorkers {
|
|
10
|
+
private redisConnectionOptions;
|
|
11
|
+
private singletonServices;
|
|
12
|
+
private createSessionServices?;
|
|
13
|
+
readonly name = "bull";
|
|
14
|
+
readonly supportsResults = true;
|
|
15
|
+
/**
|
|
16
|
+
* Configuration mapping rules for BullMQ
|
|
17
|
+
* This defines how Pikku worker configs map to BullMQ configurations
|
|
18
|
+
*/
|
|
19
|
+
readonly configMappings: QueueConfigMapping;
|
|
20
|
+
private workers;
|
|
21
|
+
private queueEvents;
|
|
22
|
+
constructor(redisConnectionOptions: ConnectionOptions, singletonServices: CoreSingletonServices, createSessionServices?: CreateSessionServices<CoreSingletonServices, CoreServices, any> | undefined);
|
|
23
|
+
/**
|
|
24
|
+
* Scan state and register all compatible processors
|
|
25
|
+
*/
|
|
26
|
+
registerQueues(): Promise<Record<string, ConfigValidationResult[]>>;
|
|
27
|
+
/**
|
|
28
|
+
* Close all queues and connections
|
|
29
|
+
*/
|
|
30
|
+
close(): Promise<void>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { Worker, } from 'bullmq';
|
|
2
|
+
import { runQueueJob, registerQueueWorkers, QueueJobFailedError, QueueJobDiscardedError, } from '@pikku/core/queue';
|
|
3
|
+
import { mapBullJobToQueueJob } from './utils.js';
|
|
4
|
+
export const mapPikkuWorkerToBull = (workerConfig) => {
|
|
5
|
+
const workerOptions = {};
|
|
6
|
+
// Direct mappings
|
|
7
|
+
if (workerConfig?.name !== undefined) {
|
|
8
|
+
workerOptions.name = workerConfig.name;
|
|
9
|
+
}
|
|
10
|
+
if (workerConfig?.batchSize !== undefined) {
|
|
11
|
+
workerOptions.concurrency = workerConfig.batchSize;
|
|
12
|
+
}
|
|
13
|
+
if (workerConfig?.autorun !== undefined) {
|
|
14
|
+
workerOptions.autorun = workerConfig.autorun;
|
|
15
|
+
}
|
|
16
|
+
if (workerConfig?.lockDuration !== undefined) {
|
|
17
|
+
workerOptions.lockDuration = workerConfig.lockDuration;
|
|
18
|
+
}
|
|
19
|
+
if (workerConfig?.drainDelay !== undefined) {
|
|
20
|
+
workerOptions.drainDelay = workerConfig.drainDelay;
|
|
21
|
+
}
|
|
22
|
+
if (workerConfig?.maxStalledCount !== undefined) {
|
|
23
|
+
workerOptions.maxStalledCount = workerConfig.maxStalledCount;
|
|
24
|
+
}
|
|
25
|
+
// Job retention
|
|
26
|
+
if (workerConfig?.removeOnComplete !== undefined) {
|
|
27
|
+
workerOptions.removeOnComplete = { count: workerConfig.removeOnComplete };
|
|
28
|
+
}
|
|
29
|
+
if (workerConfig?.removeOnFail !== undefined) {
|
|
30
|
+
workerOptions.removeOnFail = { count: workerConfig.removeOnFail };
|
|
31
|
+
}
|
|
32
|
+
return workerOptions;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Bull/Redis queue service implementation
|
|
36
|
+
* Supports job results, retries, and most queue features
|
|
37
|
+
*/
|
|
38
|
+
export class BullQueueWorkers {
|
|
39
|
+
redisConnectionOptions;
|
|
40
|
+
singletonServices;
|
|
41
|
+
createSessionServices;
|
|
42
|
+
name = 'bull';
|
|
43
|
+
supportsResults = true;
|
|
44
|
+
/**
|
|
45
|
+
* Configuration mapping rules for BullMQ
|
|
46
|
+
* This defines how Pikku worker configs map to BullMQ configurations
|
|
47
|
+
*/
|
|
48
|
+
configMappings = {
|
|
49
|
+
// Configurations that are directly supported
|
|
50
|
+
supported: {
|
|
51
|
+
name: {
|
|
52
|
+
queueProperty: 'name',
|
|
53
|
+
description: 'Worker name for identification and monitoring',
|
|
54
|
+
},
|
|
55
|
+
batchSize: {
|
|
56
|
+
queueProperty: 'concurrency',
|
|
57
|
+
description: "Number of jobs to process concurrently, this is Bull's concurrency setting",
|
|
58
|
+
},
|
|
59
|
+
autorun: {
|
|
60
|
+
queueProperty: 'autorun',
|
|
61
|
+
description: 'Whether to start processing jobs automatically',
|
|
62
|
+
},
|
|
63
|
+
lockDuration: {
|
|
64
|
+
queueProperty: 'lockDuration',
|
|
65
|
+
description: 'Duration of job lock in milliseconds',
|
|
66
|
+
},
|
|
67
|
+
drainDelay: {
|
|
68
|
+
queueProperty: 'drainDelay',
|
|
69
|
+
description: 'Delay when queue is empty before polling again',
|
|
70
|
+
},
|
|
71
|
+
maxStalledCount: {
|
|
72
|
+
queueProperty: 'maxStalledCount',
|
|
73
|
+
description: 'Maximum number of times a job can be recovered from stalled state',
|
|
74
|
+
},
|
|
75
|
+
removeOnComplete: {
|
|
76
|
+
queueProperty: 'removeOnComplete',
|
|
77
|
+
description: 'Number of completed jobs to keep for inspection',
|
|
78
|
+
},
|
|
79
|
+
removeOnFail: {
|
|
80
|
+
queueProperty: 'removeOnFail',
|
|
81
|
+
description: 'Number of failed jobs to keep for inspection',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
// Configurations that are not supported
|
|
85
|
+
unsupported: {
|
|
86
|
+
visibilityTimeout: {
|
|
87
|
+
reason: 'Bull does not use visibility timeout',
|
|
88
|
+
explanation: 'BullMQ locks jobs during processing instead of using visibility timeouts',
|
|
89
|
+
},
|
|
90
|
+
pollInterval: {
|
|
91
|
+
reason: 'Bull is push-based and does not use polling intervals',
|
|
92
|
+
explanation: 'BullMQ uses Redis pub/sub for real-time job notifications',
|
|
93
|
+
},
|
|
94
|
+
prefetch: {
|
|
95
|
+
reason: 'Bull manages job prefetching internally',
|
|
96
|
+
explanation: 'BullMQ optimizes job fetching automatically based on concurrency settings',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
// No fallback configurations for BullMQ currently
|
|
100
|
+
fallbacks: {},
|
|
101
|
+
};
|
|
102
|
+
workers = new Map();
|
|
103
|
+
queueEvents = new Map();
|
|
104
|
+
constructor(redisConnectionOptions, singletonServices, createSessionServices) {
|
|
105
|
+
this.redisConnectionOptions = redisConnectionOptions;
|
|
106
|
+
this.singletonServices = singletonServices;
|
|
107
|
+
this.createSessionServices = createSessionServices;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Scan state and register all compatible processors
|
|
111
|
+
*/
|
|
112
|
+
async registerQueues() {
|
|
113
|
+
return await registerQueueWorkers(this.configMappings, this.singletonServices.logger, async (queueName, processor) => {
|
|
114
|
+
const worker = new Worker(processor.queueName, async (job) => {
|
|
115
|
+
try {
|
|
116
|
+
return await runQueueJob({
|
|
117
|
+
singletonServices: this.singletonServices,
|
|
118
|
+
createSessionServices: this.createSessionServices,
|
|
119
|
+
job: await mapBullJobToQueueJob(job, this.redisConnectionOptions, this.queueEvents),
|
|
120
|
+
updateProgress: async (progress) => {
|
|
121
|
+
await job.updateProgress(progress);
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (error instanceof QueueJobFailedError) {
|
|
127
|
+
// Let BullMQ handle this as a failed job
|
|
128
|
+
throw new Error(error.message);
|
|
129
|
+
}
|
|
130
|
+
else if (error instanceof QueueJobDiscardedError) {
|
|
131
|
+
return; // Don't throw, job is discarded, which I guess is considered
|
|
132
|
+
// a successful job removal
|
|
133
|
+
}
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
}, {
|
|
137
|
+
connection: this.redisConnectionOptions,
|
|
138
|
+
...mapPikkuWorkerToBull(processor.config),
|
|
139
|
+
});
|
|
140
|
+
worker.on('error', console.error);
|
|
141
|
+
this.workers.set(queueName, worker);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Close all queues and connections
|
|
146
|
+
*/
|
|
147
|
+
async close() {
|
|
148
|
+
await Promise.all([
|
|
149
|
+
...Array.from(this.workers.values()).map((worker) => worker.close()),
|
|
150
|
+
...Array.from(this.queueEvents.values()).map((queueEvents) => queueEvents.close()),
|
|
151
|
+
]);
|
|
152
|
+
this.workers.clear();
|
|
153
|
+
this.queueEvents.clear();
|
|
154
|
+
}
|
|
155
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @pikku/queue-bullmq
|
|
3
|
+
*
|
|
4
|
+
* Bull/Redis queue service for Pikku
|
|
5
|
+
* Provides job results, retries, priorities, and most queue features
|
|
6
|
+
*/
|
|
7
|
+
export { BullQueueService } from './bull-queue-service.js';
|
|
8
|
+
export { BullQueueWorkers } from './bull-queue-worker.js';
|
|
9
|
+
export type { QueueService, PikkuWorkerConfig, PikkuJobConfig, QueueJob, JobOptions, } from '@pikku/core/queue';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @pikku/queue-bullmq
|
|
3
|
+
*
|
|
4
|
+
* Bull/Redis queue service for Pikku
|
|
5
|
+
* Provides job results, retries, priorities, and most queue features
|
|
6
|
+
*/
|
|
7
|
+
export { BullQueueService } from './bull-queue-service.js';
|
|
8
|
+
export { BullQueueWorkers } from './bull-queue-worker.js';
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { QueueJob } from '@pikku/core/queue';
|
|
2
|
+
import { ConnectionOptions, Job, QueueEvents } from 'bullmq';
|
|
3
|
+
/**
|
|
4
|
+
* Map Bull job to QueueJob interface
|
|
5
|
+
*/
|
|
6
|
+
export declare const mapBullJobToQueueJob: <In, Out>(bullJob: Job, redisConnection: ConnectionOptions | undefined, queueEventsMap: Map<string, QueueEvents>) => Promise<QueueJob<In, Out>>;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { QueueEvents } from 'bullmq';
|
|
2
|
+
/**
|
|
3
|
+
* Map Bull job state to our queue job status
|
|
4
|
+
*/
|
|
5
|
+
const mapBullStateToStatus = (state) => {
|
|
6
|
+
switch (state) {
|
|
7
|
+
case 'waiting':
|
|
8
|
+
return 'waiting';
|
|
9
|
+
case 'active':
|
|
10
|
+
return 'active';
|
|
11
|
+
case 'completed':
|
|
12
|
+
return 'completed';
|
|
13
|
+
case 'failed':
|
|
14
|
+
return 'failed';
|
|
15
|
+
case 'delayed':
|
|
16
|
+
return 'delayed';
|
|
17
|
+
default:
|
|
18
|
+
return 'waiting';
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Map Bull job to QueueJob interface
|
|
23
|
+
*/
|
|
24
|
+
export const mapBullJobToQueueJob = async (bullJob, redisConnection, queueEventsMap) => {
|
|
25
|
+
return {
|
|
26
|
+
queueName: bullJob.name,
|
|
27
|
+
id: bullJob.id || '',
|
|
28
|
+
data: bullJob.data,
|
|
29
|
+
result: bullJob.returnvalue,
|
|
30
|
+
status: async () => mapBullStateToStatus(await bullJob.getState()),
|
|
31
|
+
waitForCompletion: async (ttl) => {
|
|
32
|
+
let queueEvents = queueEventsMap.get(bullJob.name);
|
|
33
|
+
if (!queueEvents) {
|
|
34
|
+
queueEvents = new QueueEvents(bullJob.name, redisConnection ? { connection: redisConnection } : undefined);
|
|
35
|
+
queueEventsMap.set(bullJob.name, queueEvents);
|
|
36
|
+
}
|
|
37
|
+
return await bullJob.waitUntilFinished(queueEvents, ttl);
|
|
38
|
+
},
|
|
39
|
+
metadata: () => ({
|
|
40
|
+
progress: bullJob.progress,
|
|
41
|
+
attemptsMade: bullJob.attemptsMade,
|
|
42
|
+
maxAttempts: bullJob.opts.attempts || 1,
|
|
43
|
+
createdAt: new Date(bullJob.timestamp),
|
|
44
|
+
processedAt: bullJob.processedOn
|
|
45
|
+
? new Date(bullJob.processedOn)
|
|
46
|
+
: undefined,
|
|
47
|
+
completedAt: bullJob.finishedOn
|
|
48
|
+
? new Date(bullJob.finishedOn)
|
|
49
|
+
: undefined,
|
|
50
|
+
failedAt: bullJob.finishedOn ? new Date(bullJob.finishedOn) : undefined,
|
|
51
|
+
error: bullJob.failedReason,
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/queue-bullmq",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.5-next.0",
|
|
4
4
|
"author": "yasser.fadl@gmail.com",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"release": "npm run build && npm test"
|
|
14
14
|
},
|
|
15
15
|
"peerDependencies": {
|
|
16
|
-
"@pikku/core": "^0.9.
|
|
16
|
+
"@pikku/core": "^0.9.12-next.0"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"bullmq": "^5.57.0",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/bull-queue-service.ts","./src/bull-queue-worker.ts","./src/index.ts","./src/utils.ts"],"version":"5.9.2"}
|