@pikku/queue-bullmq 0.9.4 → 0.10.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 CHANGED
@@ -1,5 +1,18 @@
1
1
  # @pikku/queue-bullmq
2
2
 
3
+ ## 0.10.0
4
+
5
+ This release includes significant improvements across the framework including tree-shaking support, middleware/permission factories, enhanced CLI functionality, improved TypeScript type safety, and comprehensive test strategies.
6
+
7
+ For complete details, see https://pikku.dev/changelogs/0_10_0.md
8
+
9
+ ## 0.9.5-next.0
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies
14
+ - @pikku/core@0.9.12-next.0
15
+
3
16
  ## 0.9.4
4
17
 
5
18
  ### Patch Changes
@@ -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
+ }
@@ -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';
@@ -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.4",
3
+ "version": "0.10.0",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "module": "dist/index.js",
@@ -13,11 +13,11 @@
13
13
  "release": "npm run build && npm test"
14
14
  },
15
15
  "peerDependencies": {
16
- "@pikku/core": "^0.9.9"
16
+ "@pikku/core": "^0.10.0"
17
17
  },
18
18
  "dependencies": {
19
- "bullmq": "^5.57.0",
20
- "ioredis": "^5.7.0"
19
+ "bullmq": "^5.61.0",
20
+ "ioredis": "^5.8.2"
21
21
  },
22
22
  "devDependencies": {
23
23
  "typescript": "^5.9"
@@ -0,0 +1 @@
1
+ {"root":["./src/bull-queue-service.ts","./src/bull-queue-worker.ts","./src/index.ts","./src/utils.ts"],"version":"5.9.3"}