@pikku/queue-bullmq 0.11.2 → 0.12.1
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 +25 -2
- package/dist/bull-queue-service.d.ts +2 -1
- package/dist/bull-queue-service.js +8 -1
- package/dist/bull-queue-worker.d.ts +2 -5
- package/dist/bull-queue-worker.js +7 -11
- package/dist/bull-scheduler-service.d.ts +16 -6
- package/dist/bull-scheduler-service.js +56 -6
- package/dist/bull-service-factory.d.ts +2 -3
- package/dist/bull-service-factory.js +2 -2
- package/dist/utils.d.ts +3 -2
- package/dist/utils.js +4 -3
- package/package.json +4 -4
- package/src/bull-queue-service.ts +15 -6
- package/src/bull-queue-worker.test.ts +15 -0
- package/src/bull-queue-worker.ts +10 -17
- package/src/bull-scheduler-service.ts +70 -9
- package/src/bull-service-factory.ts +3 -11
- package/src/utils.ts +7 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
## 0.12.0
|
|
2
|
+
|
|
3
|
+
## 0.12.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e04531f: Code quality improvements: resolve oxlint warnings and apply autofixes across the codebase (unused bindings, unnecessary constructors, prefer `const` over `let`, etc.). No behaviour changes.
|
|
8
|
+
- Updated dependencies [62a8725]
|
|
9
|
+
- Updated dependencies [a3bdb0d]
|
|
10
|
+
- Updated dependencies [e0349ff]
|
|
11
|
+
- Updated dependencies [62a8725]
|
|
12
|
+
- Updated dependencies [e04531f]
|
|
13
|
+
- Updated dependencies [62a8725]
|
|
14
|
+
- Updated dependencies [a83efb8]
|
|
15
|
+
- Updated dependencies [8eed717]
|
|
16
|
+
- Updated dependencies [62a8725]
|
|
17
|
+
- Updated dependencies [62a8725]
|
|
18
|
+
- Updated dependencies [62a8725]
|
|
19
|
+
- @pikku/core@0.12.1
|
|
20
|
+
|
|
21
|
+
### New Features
|
|
22
|
+
|
|
23
|
+
- Recurring cron scheduled tasks support via `BullSchedulerService`
|
|
24
|
+
- `setServices()`, `start()`, `stop()` lifecycle methods on scheduler and queue workers
|
|
25
|
+
|
|
1
26
|
## 0.11.0
|
|
2
27
|
|
|
3
28
|
## 0.11.2
|
|
@@ -8,8 +33,6 @@
|
|
|
8
33
|
- Updated dependencies [db9c7bf]
|
|
9
34
|
- @pikku/core@0.11.2
|
|
10
35
|
|
|
11
|
-
## 0.11.2
|
|
12
|
-
|
|
13
36
|
### Fixes
|
|
14
37
|
|
|
15
38
|
- ddd87eaf: Simplify CreateWireServices type signature for custom Config compatibility
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { ConnectionOptions, JobsOptions } from 'bullmq';
|
|
2
|
+
import type Bull from 'bullmq';
|
|
2
3
|
import type { QueueService, QueueJob, JobOptions } from '@pikku/core/queue';
|
|
3
4
|
export declare const mapPikkuJobToBull: (options?: JobOptions) => JobsOptions;
|
|
4
5
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Queue,
|
|
1
|
+
import { Queue, QueueEvents, RedisConnection } from 'bullmq';
|
|
2
2
|
import { mapBullJobToQueueJob } from './utils.js';
|
|
3
3
|
export const mapPikkuJobToBull = (options) => {
|
|
4
4
|
const bullOptions = {};
|
|
@@ -53,6 +53,13 @@ export class BullQueueService {
|
|
|
53
53
|
const queue = new Queue(queueName, config, PikkuRedisConnection);
|
|
54
54
|
await queue.waitUntilReady();
|
|
55
55
|
this.queues.set(queueName, queue);
|
|
56
|
+
if (!this.queueEvents.has(queueName)) {
|
|
57
|
+
const queueEvents = new QueueEvents(queueName, this.redisConnectionOptions
|
|
58
|
+
? { connection: this.redisConnectionOptions }
|
|
59
|
+
: undefined);
|
|
60
|
+
await queueEvents.waitUntilReady();
|
|
61
|
+
this.queueEvents.set(queueName, queueEvents);
|
|
62
|
+
}
|
|
56
63
|
return queue;
|
|
57
64
|
}
|
|
58
65
|
/**
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { ConnectionOptions, WorkerOptions } from 'bullmq';
|
|
1
|
+
import type { ConnectionOptions, WorkerOptions } from 'bullmq';
|
|
2
2
|
import type { QueueWorkers, PikkuWorkerConfig, QueueConfigMapping, ConfigValidationResult } from '@pikku/core/queue';
|
|
3
|
-
import type { CoreSingletonServices, CreateWireServices } from '@pikku/core';
|
|
4
3
|
export declare const mapPikkuWorkerToBull: (workerConfig?: PikkuWorkerConfig) => Omit<WorkerOptions, "connection">;
|
|
5
4
|
/**
|
|
6
5
|
* Bull/Redis queue service implementation
|
|
@@ -8,8 +7,6 @@ export declare const mapPikkuWorkerToBull: (workerConfig?: PikkuWorkerConfig) =>
|
|
|
8
7
|
*/
|
|
9
8
|
export declare class BullQueueWorkers implements QueueWorkers {
|
|
10
9
|
private redisConnectionOptions;
|
|
11
|
-
private singletonServices;
|
|
12
|
-
private createWireServices?;
|
|
13
10
|
readonly name = "bull";
|
|
14
11
|
readonly supportsResults = true;
|
|
15
12
|
/**
|
|
@@ -19,7 +16,7 @@ export declare class BullQueueWorkers implements QueueWorkers {
|
|
|
19
16
|
readonly configMappings: QueueConfigMapping;
|
|
20
17
|
private workers;
|
|
21
18
|
private queueEvents;
|
|
22
|
-
constructor(redisConnectionOptions: ConnectionOptions
|
|
19
|
+
constructor(redisConnectionOptions: ConnectionOptions);
|
|
23
20
|
/**
|
|
24
21
|
* Scan state and register all compatible processors
|
|
25
22
|
*/
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Worker
|
|
2
|
-
import {
|
|
1
|
+
import { Worker } from 'bullmq';
|
|
2
|
+
import { registerQueueWorkers, runQueueJob, QueueJobFailedError, QueueJobDiscardedError, } from '@pikku/core/queue';
|
|
3
|
+
import { pikkuState } from '@pikku/core/internal';
|
|
3
4
|
import { mapBullJobToQueueJob } from './utils.js';
|
|
4
5
|
export const mapPikkuWorkerToBull = (workerConfig) => {
|
|
5
6
|
const workerOptions = {};
|
|
@@ -37,8 +38,6 @@ export const mapPikkuWorkerToBull = (workerConfig) => {
|
|
|
37
38
|
*/
|
|
38
39
|
export class BullQueueWorkers {
|
|
39
40
|
redisConnectionOptions;
|
|
40
|
-
singletonServices;
|
|
41
|
-
createWireServices;
|
|
42
41
|
name = 'bull';
|
|
43
42
|
supportsResults = true;
|
|
44
43
|
/**
|
|
@@ -101,21 +100,18 @@ export class BullQueueWorkers {
|
|
|
101
100
|
};
|
|
102
101
|
workers = new Map();
|
|
103
102
|
queueEvents = new Map();
|
|
104
|
-
constructor(redisConnectionOptions
|
|
103
|
+
constructor(redisConnectionOptions) {
|
|
105
104
|
this.redisConnectionOptions = redisConnectionOptions;
|
|
106
|
-
this.singletonServices = singletonServices;
|
|
107
|
-
this.createWireServices = createWireServices;
|
|
108
105
|
}
|
|
109
106
|
/**
|
|
110
107
|
* Scan state and register all compatible processors
|
|
111
108
|
*/
|
|
112
109
|
async registerQueues() {
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
const logger = pikkuState(null, 'package', 'singletonServices').logger;
|
|
111
|
+
return await registerQueueWorkers(this.configMappings, logger, async (queueName, processor) => {
|
|
112
|
+
const worker = new Worker(processor.name, async (job) => {
|
|
115
113
|
try {
|
|
116
114
|
return await runQueueJob({
|
|
117
|
-
singletonServices: this.singletonServices,
|
|
118
|
-
createWireServices: this.createWireServices,
|
|
119
115
|
job: await mapBullJobToQueueJob(job, this.redisConnectionOptions, this.queueEvents),
|
|
120
116
|
updateProgress: async (progress) => {
|
|
121
117
|
await job.updateProgress(progress);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { ConnectionOptions } from 'bullmq';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
* BullMQ scheduler service implementation
|
|
5
|
-
* Uses BullMQ's delayed jobs for one-time execution
|
|
6
|
-
*/
|
|
1
|
+
import type { ConnectionOptions } from 'bullmq';
|
|
2
|
+
import type { ScheduledTaskInfo, ScheduledTaskSummary, CoreUserSession } from '@pikku/core';
|
|
3
|
+
import { SchedulerService } from '@pikku/core';
|
|
7
4
|
export declare class BullSchedulerService extends SchedulerService {
|
|
5
|
+
private redisConnectionOptions;
|
|
8
6
|
private queue;
|
|
7
|
+
private recurringQueue;
|
|
8
|
+
private recurringWorker?;
|
|
9
|
+
private repeatJobKeys;
|
|
9
10
|
constructor(redisConnectionOptions: ConnectionOptions);
|
|
10
11
|
/**
|
|
11
12
|
* Initialize - wait for queue to be ready
|
|
@@ -32,4 +33,13 @@ export declare class BullSchedulerService extends SchedulerService {
|
|
|
32
33
|
* Close the queue connection
|
|
33
34
|
*/
|
|
34
35
|
close(): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Start recurring scheduled tasks.
|
|
38
|
+
* Creates a BullMQ Worker to process repeat jobs via runScheduledTask.
|
|
39
|
+
*/
|
|
40
|
+
start(): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Stop recurring scheduled tasks by removing repeat jobs.
|
|
43
|
+
*/
|
|
44
|
+
stop(): Promise<void>;
|
|
35
45
|
}
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
import { Queue } from 'bullmq';
|
|
2
|
-
import { SchedulerService, parseDurationString
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
*/
|
|
1
|
+
import { Queue, Worker } from 'bullmq';
|
|
2
|
+
import { SchedulerService, parseDurationString } from '@pikku/core';
|
|
3
|
+
import { pikkuState } from '@pikku/core/internal';
|
|
4
|
+
import { runScheduledTask, getScheduledTasks } from '@pikku/core/scheduler';
|
|
5
|
+
const RECURRING_QUEUE_NAME = 'pikku-recurring-scheduled-task';
|
|
7
6
|
export class BullSchedulerService extends SchedulerService {
|
|
7
|
+
redisConnectionOptions;
|
|
8
8
|
queue;
|
|
9
|
+
recurringQueue;
|
|
10
|
+
recurringWorker;
|
|
11
|
+
repeatJobKeys = [];
|
|
9
12
|
constructor(redisConnectionOptions) {
|
|
10
13
|
super();
|
|
14
|
+
this.redisConnectionOptions = redisConnectionOptions;
|
|
11
15
|
this.queue = new Queue('pikku-remote-internal-rpc', {
|
|
12
16
|
connection: redisConnectionOptions,
|
|
13
17
|
});
|
|
18
|
+
this.recurringQueue = new Queue(RECURRING_QUEUE_NAME, {
|
|
19
|
+
connection: redisConnectionOptions,
|
|
20
|
+
});
|
|
14
21
|
}
|
|
15
22
|
/**
|
|
16
23
|
* Initialize - wait for queue to be ready
|
|
@@ -95,6 +102,49 @@ export class BullSchedulerService extends SchedulerService {
|
|
|
95
102
|
* Close the queue connection
|
|
96
103
|
*/
|
|
97
104
|
async close() {
|
|
105
|
+
if (this.recurringWorker) {
|
|
106
|
+
await this.recurringWorker.close();
|
|
107
|
+
}
|
|
108
|
+
await this.recurringQueue.close();
|
|
98
109
|
await this.queue.close();
|
|
99
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Start recurring scheduled tasks.
|
|
113
|
+
* Creates a BullMQ Worker to process repeat jobs via runScheduledTask.
|
|
114
|
+
*/
|
|
115
|
+
async start() {
|
|
116
|
+
const logger = pikkuState(null, 'package', 'singletonServices').logger;
|
|
117
|
+
const scheduledTasks = getScheduledTasks();
|
|
118
|
+
// Create a worker to process recurring scheduled task jobs
|
|
119
|
+
this.recurringWorker = new Worker(RECURRING_QUEUE_NAME, async (job) => {
|
|
120
|
+
const { rpcName } = job.data;
|
|
121
|
+
logger.info(`Running scheduled task: ${rpcName}`);
|
|
122
|
+
await runScheduledTask({ name: rpcName });
|
|
123
|
+
}, { connection: this.redisConnectionOptions });
|
|
124
|
+
this.recurringWorker.on('error', (err) => {
|
|
125
|
+
logger.error(`Recurring task worker error: ${err}`);
|
|
126
|
+
});
|
|
127
|
+
for (const [name, task] of scheduledTasks) {
|
|
128
|
+
const job = await this.recurringQueue.add(name, { rpcName: name }, {
|
|
129
|
+
repeat: { pattern: task.schedule },
|
|
130
|
+
jobId: `recurring:${name}`,
|
|
131
|
+
});
|
|
132
|
+
if (job.repeatJobKey) {
|
|
133
|
+
this.repeatJobKeys.push(job.repeatJobKey);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Stop recurring scheduled tasks by removing repeat jobs.
|
|
139
|
+
*/
|
|
140
|
+
async stop() {
|
|
141
|
+
for (const key of this.repeatJobKeys) {
|
|
142
|
+
await this.recurringQueue.removeRepeatableByKey(key);
|
|
143
|
+
}
|
|
144
|
+
this.repeatJobKeys = [];
|
|
145
|
+
if (this.recurringWorker) {
|
|
146
|
+
await this.recurringWorker.close();
|
|
147
|
+
this.recurringWorker = undefined;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
100
150
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { ConnectionOptions } from 'bullmq';
|
|
1
|
+
import type { ConnectionOptions } from 'bullmq';
|
|
2
2
|
import { BullQueueService } from './bull-queue-service.js';
|
|
3
3
|
import { BullQueueWorkers } from './bull-queue-worker.js';
|
|
4
4
|
import { BullSchedulerService } from './bull-scheduler-service.js';
|
|
5
|
-
import type { CoreSingletonServices, CreateWireServices } from '@pikku/core';
|
|
6
5
|
/**
|
|
7
6
|
* Factory class for BullMQ services
|
|
8
7
|
* Manages Redis connection options shared across queue, worker, and scheduler services
|
|
@@ -24,7 +23,7 @@ export declare class BullServiceFactory {
|
|
|
24
23
|
/**
|
|
25
24
|
* Get the queue workers for processing jobs
|
|
26
25
|
*/
|
|
27
|
-
getQueueWorkers(
|
|
26
|
+
getQueueWorkers(): BullQueueWorkers;
|
|
28
27
|
/**
|
|
29
28
|
* Get the scheduler service for managing delayed tasks
|
|
30
29
|
*/
|
|
@@ -31,9 +31,9 @@ export class BullServiceFactory {
|
|
|
31
31
|
/**
|
|
32
32
|
* Get the queue workers for processing jobs
|
|
33
33
|
*/
|
|
34
|
-
getQueueWorkers(
|
|
34
|
+
getQueueWorkers() {
|
|
35
35
|
if (!this.queueWorkers) {
|
|
36
|
-
this.queueWorkers = new BullQueueWorkers(this.redisConnectionOptions
|
|
36
|
+
this.queueWorkers = new BullQueueWorkers(this.redisConnectionOptions);
|
|
37
37
|
}
|
|
38
38
|
return this.queueWorkers;
|
|
39
39
|
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { QueueJob } from '@pikku/core/queue';
|
|
2
|
-
import { ConnectionOptions, Job
|
|
1
|
+
import type { QueueJob } from '@pikku/core/queue';
|
|
2
|
+
import type { ConnectionOptions, Job } from 'bullmq';
|
|
3
|
+
import { QueueEvents } from 'bullmq';
|
|
3
4
|
/**
|
|
4
5
|
* Map Bull job to QueueJob interface
|
|
5
6
|
*/
|
package/dist/utils.js
CHANGED
|
@@ -29,10 +29,11 @@ export const mapBullJobToQueueJob = async (bullJob, redisConnection, queueEvents
|
|
|
29
29
|
result: bullJob.returnvalue,
|
|
30
30
|
status: async () => mapBullStateToStatus(await bullJob.getState()),
|
|
31
31
|
waitForCompletion: async (ttl) => {
|
|
32
|
-
let queueEvents = queueEventsMap.get(bullJob.
|
|
32
|
+
let queueEvents = queueEventsMap.get(bullJob.queueName);
|
|
33
33
|
if (!queueEvents) {
|
|
34
|
-
queueEvents = new QueueEvents(bullJob.
|
|
35
|
-
queueEventsMap.set(bullJob.
|
|
34
|
+
queueEvents = new QueueEvents(bullJob.queueName, redisConnection ? { connection: redisConnection } : undefined);
|
|
35
|
+
queueEventsMap.set(bullJob.queueName, queueEvents);
|
|
36
|
+
await queueEvents.waitUntilReady();
|
|
36
37
|
}
|
|
37
38
|
return await bullJob.waitUntilFinished(queueEvents, ttl);
|
|
38
39
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/queue-bullmq",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
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.
|
|
16
|
+
"@pikku/core": "^0.12.1"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"bullmq": "^5.
|
|
20
|
-
"ioredis": "^5.
|
|
19
|
+
"bullmq": "^5.67.3",
|
|
20
|
+
"ioredis": "^5.9.2"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"typescript": "^5.9"
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
Queue,
|
|
5
|
-
RedisConnection,
|
|
6
|
-
} from 'bullmq'
|
|
1
|
+
import type { ConnectionOptions, JobsOptions } from 'bullmq'
|
|
2
|
+
import type Bull from 'bullmq'
|
|
3
|
+
import { Queue, QueueEvents, RedisConnection } from 'bullmq'
|
|
7
4
|
import type { QueueService, QueueJob, JobOptions } from '@pikku/core/queue'
|
|
8
5
|
import { mapBullJobToQueueJob } from './utils.js'
|
|
9
6
|
|
|
@@ -73,6 +70,18 @@ export class BullQueueService implements QueueService {
|
|
|
73
70
|
await queue.waitUntilReady()
|
|
74
71
|
|
|
75
72
|
this.queues.set(queueName, queue)
|
|
73
|
+
|
|
74
|
+
if (!this.queueEvents.has(queueName)) {
|
|
75
|
+
const queueEvents = new QueueEvents(
|
|
76
|
+
queueName,
|
|
77
|
+
this.redisConnectionOptions
|
|
78
|
+
? { connection: this.redisConnectionOptions }
|
|
79
|
+
: undefined
|
|
80
|
+
)
|
|
81
|
+
await queueEvents.waitUntilReady()
|
|
82
|
+
this.queueEvents.set(queueName, queueEvents)
|
|
83
|
+
}
|
|
84
|
+
|
|
76
85
|
return queue
|
|
77
86
|
}
|
|
78
87
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import { BullQueueWorkers } from './bull-queue-worker.js'
|
|
5
|
+
|
|
6
|
+
describe('BullQueueWorkers', () => {
|
|
7
|
+
test('requires setJobRunner before registerQueues', async () => {
|
|
8
|
+
const workers = new BullQueueWorkers({})
|
|
9
|
+
|
|
10
|
+
await assert.rejects(() => workers.registerQueues(), {
|
|
11
|
+
message:
|
|
12
|
+
'BullQueueWorkers requires setJobRunner() before registerQueues()',
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
})
|
package/src/bull-queue-worker.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
Worker,
|
|
5
|
-
QueueEvents,
|
|
6
|
-
} from 'bullmq'
|
|
1
|
+
import type { ConnectionOptions, WorkerOptions, QueueEvents } from 'bullmq'
|
|
2
|
+
import type Bull from 'bullmq'
|
|
3
|
+
import { Worker } from 'bullmq'
|
|
7
4
|
import type {
|
|
8
5
|
QueueWorkers,
|
|
9
6
|
PikkuWorkerConfig,
|
|
@@ -11,12 +8,12 @@ import type {
|
|
|
11
8
|
ConfigValidationResult,
|
|
12
9
|
} from '@pikku/core/queue'
|
|
13
10
|
import {
|
|
14
|
-
runQueueJob,
|
|
15
11
|
registerQueueWorkers,
|
|
12
|
+
runQueueJob,
|
|
16
13
|
QueueJobFailedError,
|
|
17
14
|
QueueJobDiscardedError,
|
|
18
15
|
} from '@pikku/core/queue'
|
|
19
|
-
import
|
|
16
|
+
import { pikkuState } from '@pikku/core/internal'
|
|
20
17
|
import { mapBullJobToQueueJob } from './utils.js'
|
|
21
18
|
|
|
22
19
|
export const mapPikkuWorkerToBull = (
|
|
@@ -138,27 +135,23 @@ export class BullQueueWorkers implements QueueWorkers {
|
|
|
138
135
|
private workers = new Map<string, Bull.Worker>()
|
|
139
136
|
private queueEvents = new Map<string, QueueEvents>()
|
|
140
137
|
|
|
141
|
-
constructor(
|
|
142
|
-
private redisConnectionOptions: ConnectionOptions,
|
|
143
|
-
private singletonServices: CoreSingletonServices,
|
|
144
|
-
private createWireServices?: CreateWireServices
|
|
145
|
-
) {}
|
|
138
|
+
constructor(private redisConnectionOptions: ConnectionOptions) {}
|
|
146
139
|
|
|
147
140
|
/**
|
|
148
141
|
* Scan state and register all compatible processors
|
|
149
142
|
*/
|
|
150
143
|
async registerQueues(): Promise<Record<string, ConfigValidationResult[]>> {
|
|
144
|
+
const logger = pikkuState(null, 'package', 'singletonServices')!.logger
|
|
145
|
+
|
|
151
146
|
return await registerQueueWorkers(
|
|
152
147
|
this.configMappings,
|
|
153
|
-
|
|
148
|
+
logger,
|
|
154
149
|
async (queueName, processor) => {
|
|
155
150
|
const worker = new Worker(
|
|
156
|
-
processor.
|
|
151
|
+
processor.name,
|
|
157
152
|
async (job: Bull.Job) => {
|
|
158
153
|
try {
|
|
159
154
|
return await runQueueJob({
|
|
160
|
-
singletonServices: this.singletonServices,
|
|
161
|
-
createWireServices: this.createWireServices,
|
|
162
155
|
job: await mapBullJobToQueueJob(
|
|
163
156
|
job,
|
|
164
157
|
this.redisConnectionOptions,
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { ConnectionOptions
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import type { ConnectionOptions } from 'bullmq'
|
|
2
|
+
import { Queue, Worker } from 'bullmq'
|
|
3
|
+
import type {
|
|
4
4
|
ScheduledTaskInfo,
|
|
5
5
|
ScheduledTaskSummary,
|
|
6
6
|
CoreUserSession,
|
|
7
|
-
parseDurationString,
|
|
8
7
|
} from '@pikku/core'
|
|
8
|
+
import { SchedulerService, parseDurationString } from '@pikku/core'
|
|
9
|
+
import { pikkuState } from '@pikku/core/internal'
|
|
10
|
+
import { runScheduledTask, getScheduledTasks } from '@pikku/core/scheduler'
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Data stored in scheduled job
|
|
@@ -16,18 +18,22 @@ interface ScheduledJobData {
|
|
|
16
18
|
session?: CoreUserSession
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* Uses BullMQ's delayed jobs for one-time execution
|
|
22
|
-
*/
|
|
21
|
+
const RECURRING_QUEUE_NAME = 'pikku-recurring-scheduled-task'
|
|
22
|
+
|
|
23
23
|
export class BullSchedulerService extends SchedulerService {
|
|
24
24
|
private queue: Queue
|
|
25
|
+
private recurringQueue: Queue
|
|
26
|
+
private recurringWorker?: Worker
|
|
27
|
+
private repeatJobKeys: string[] = []
|
|
25
28
|
|
|
26
|
-
constructor(redisConnectionOptions: ConnectionOptions) {
|
|
29
|
+
constructor(private redisConnectionOptions: ConnectionOptions) {
|
|
27
30
|
super()
|
|
28
31
|
this.queue = new Queue('pikku-remote-internal-rpc', {
|
|
29
32
|
connection: redisConnectionOptions,
|
|
30
33
|
})
|
|
34
|
+
this.recurringQueue = new Queue(RECURRING_QUEUE_NAME, {
|
|
35
|
+
connection: redisConnectionOptions,
|
|
36
|
+
})
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
/**
|
|
@@ -134,6 +140,61 @@ export class BullSchedulerService extends SchedulerService {
|
|
|
134
140
|
* Close the queue connection
|
|
135
141
|
*/
|
|
136
142
|
async close(): Promise<void> {
|
|
143
|
+
if (this.recurringWorker) {
|
|
144
|
+
await this.recurringWorker.close()
|
|
145
|
+
}
|
|
146
|
+
await this.recurringQueue.close()
|
|
137
147
|
await this.queue.close()
|
|
138
148
|
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Start recurring scheduled tasks.
|
|
152
|
+
* Creates a BullMQ Worker to process repeat jobs via runScheduledTask.
|
|
153
|
+
*/
|
|
154
|
+
async start(): Promise<void> {
|
|
155
|
+
const logger = pikkuState(null, 'package', 'singletonServices')!.logger
|
|
156
|
+
const scheduledTasks = getScheduledTasks()
|
|
157
|
+
|
|
158
|
+
// Create a worker to process recurring scheduled task jobs
|
|
159
|
+
this.recurringWorker = new Worker(
|
|
160
|
+
RECURRING_QUEUE_NAME,
|
|
161
|
+
async (job) => {
|
|
162
|
+
const { rpcName } = job.data as ScheduledJobData
|
|
163
|
+
logger.info(`Running scheduled task: ${rpcName}`)
|
|
164
|
+
await runScheduledTask({ name: rpcName })
|
|
165
|
+
},
|
|
166
|
+
{ connection: this.redisConnectionOptions }
|
|
167
|
+
)
|
|
168
|
+
this.recurringWorker.on('error', (err) => {
|
|
169
|
+
logger.error(`Recurring task worker error: ${err}`)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
for (const [name, task] of scheduledTasks) {
|
|
173
|
+
const job = await this.recurringQueue.add(
|
|
174
|
+
name,
|
|
175
|
+
{ rpcName: name } as ScheduledJobData,
|
|
176
|
+
{
|
|
177
|
+
repeat: { pattern: task.schedule },
|
|
178
|
+
jobId: `recurring:${name}`,
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
if (job.repeatJobKey) {
|
|
182
|
+
this.repeatJobKeys.push(job.repeatJobKey)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Stop recurring scheduled tasks by removing repeat jobs.
|
|
189
|
+
*/
|
|
190
|
+
async stop(): Promise<void> {
|
|
191
|
+
for (const key of this.repeatJobKeys) {
|
|
192
|
+
await this.recurringQueue.removeRepeatableByKey(key)
|
|
193
|
+
}
|
|
194
|
+
this.repeatJobKeys = []
|
|
195
|
+
if (this.recurringWorker) {
|
|
196
|
+
await this.recurringWorker.close()
|
|
197
|
+
this.recurringWorker = undefined
|
|
198
|
+
}
|
|
199
|
+
}
|
|
139
200
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { ConnectionOptions } from 'bullmq'
|
|
1
|
+
import type { ConnectionOptions } from 'bullmq'
|
|
2
2
|
import { BullQueueService } from './bull-queue-service.js'
|
|
3
3
|
import { BullQueueWorkers } from './bull-queue-worker.js'
|
|
4
4
|
import { BullSchedulerService } from './bull-scheduler-service.js'
|
|
5
|
-
import type { CoreSingletonServices, CreateWireServices } from '@pikku/core'
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Factory class for BullMQ services
|
|
@@ -35,16 +34,9 @@ export class BullServiceFactory {
|
|
|
35
34
|
/**
|
|
36
35
|
* Get the queue workers for processing jobs
|
|
37
36
|
*/
|
|
38
|
-
getQueueWorkers(
|
|
39
|
-
singletonServices: CoreSingletonServices,
|
|
40
|
-
createWireServices?: CreateWireServices
|
|
41
|
-
): BullQueueWorkers {
|
|
37
|
+
getQueueWorkers(): BullQueueWorkers {
|
|
42
38
|
if (!this.queueWorkers) {
|
|
43
|
-
this.queueWorkers = new BullQueueWorkers(
|
|
44
|
-
this.redisConnectionOptions,
|
|
45
|
-
singletonServices,
|
|
46
|
-
createWireServices
|
|
47
|
-
)
|
|
39
|
+
this.queueWorkers = new BullQueueWorkers(this.redisConnectionOptions)
|
|
48
40
|
}
|
|
49
41
|
return this.queueWorkers
|
|
50
42
|
}
|
package/src/utils.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { QueueJob, QueueJobStatus } from '@pikku/core/queue'
|
|
2
|
-
import { ConnectionOptions, Job
|
|
1
|
+
import type { QueueJob, QueueJobStatus } from '@pikku/core/queue'
|
|
2
|
+
import type { ConnectionOptions, Job } from 'bullmq'
|
|
3
|
+
import { QueueEvents } from 'bullmq'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Map Bull job state to our queue job status
|
|
@@ -36,13 +37,14 @@ export const mapBullJobToQueueJob = async <In, Out>(
|
|
|
36
37
|
result: bullJob.returnvalue as Out,
|
|
37
38
|
status: async () => mapBullStateToStatus(await bullJob.getState()),
|
|
38
39
|
waitForCompletion: async (ttl?: number): Promise<Out> => {
|
|
39
|
-
let queueEvents = queueEventsMap.get(bullJob.
|
|
40
|
+
let queueEvents = queueEventsMap.get(bullJob.queueName)
|
|
40
41
|
if (!queueEvents) {
|
|
41
42
|
queueEvents = new QueueEvents(
|
|
42
|
-
bullJob.
|
|
43
|
+
bullJob.queueName,
|
|
43
44
|
redisConnection ? { connection: redisConnection } : undefined
|
|
44
45
|
)
|
|
45
|
-
queueEventsMap.set(bullJob.
|
|
46
|
+
queueEventsMap.set(bullJob.queueName, queueEvents)
|
|
47
|
+
await queueEvents.waitUntilReady()
|
|
46
48
|
}
|
|
47
49
|
return await bullJob.waitUntilFinished(queueEvents, ttl)
|
|
48
50
|
},
|