@pikku/queue-bullmq 0.11.1 → 0.12.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 +19 -0
- package/dist/bull-queue-worker.d.ts +9 -5
- package/dist/bull-queue-worker.js +14 -11
- package/dist/bull-scheduler-service.d.ts +21 -5
- package/dist/bull-scheduler-service.js +65 -5
- package/dist/bull-service-factory.d.ts +7 -2
- package/dist/bull-service-factory.js +16 -1
- package/dist/utils.js +4 -3
- package/package.json +4 -4
- package/src/bull-queue-worker.test.ts +15 -0
- package/src/bull-queue-worker.ts +31 -21
- package/src/bull-scheduler-service.ts +82 -6
- package/src/bull-service-factory.ts +28 -15
- package/src/utils.ts +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
|
+
## 0.12.0
|
|
2
|
+
|
|
3
|
+
### New Features
|
|
4
|
+
|
|
5
|
+
- Recurring cron scheduled tasks support via `BullSchedulerService`
|
|
6
|
+
- `setServices()`, `start()`, `stop()` lifecycle methods on scheduler and queue workers
|
|
7
|
+
|
|
1
8
|
## 0.11.0
|
|
2
9
|
|
|
10
|
+
## 0.11.2
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- db9c7bf: Simplify CreateWireServices type signature for custom Config compatibility
|
|
15
|
+
- Updated dependencies [db9c7bf]
|
|
16
|
+
- @pikku/core@0.11.2
|
|
17
|
+
|
|
18
|
+
### Fixes
|
|
19
|
+
|
|
20
|
+
- ddd87eaf: Simplify CreateWireServices type signature for custom Config compatibility
|
|
21
|
+
|
|
3
22
|
## 0.11.1
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ConnectionOptions, WorkerOptions } from 'bullmq';
|
|
2
|
-
import type { QueueWorkers, PikkuWorkerConfig, QueueConfigMapping, ConfigValidationResult } from '@pikku/core/queue';
|
|
3
|
-
import type {
|
|
2
|
+
import type { QueueWorkers, PikkuWorkerConfig, QueueConfigMapping, ConfigValidationResult, QueueJob } from '@pikku/core/queue';
|
|
3
|
+
import type { Logger } from '@pikku/core/services';
|
|
4
4
|
export declare const mapPikkuWorkerToBull: (workerConfig?: PikkuWorkerConfig) => Omit<WorkerOptions, "connection">;
|
|
5
5
|
/**
|
|
6
6
|
* Bull/Redis queue service implementation
|
|
@@ -8,8 +8,6 @@ export declare const mapPikkuWorkerToBull: (workerConfig?: PikkuWorkerConfig) =>
|
|
|
8
8
|
*/
|
|
9
9
|
export declare class BullQueueWorkers implements QueueWorkers {
|
|
10
10
|
private redisConnectionOptions;
|
|
11
|
-
private singletonServices;
|
|
12
|
-
private createWireServices?;
|
|
13
11
|
readonly name = "bull";
|
|
14
12
|
readonly supportsResults = true;
|
|
15
13
|
/**
|
|
@@ -19,7 +17,13 @@ export declare class BullQueueWorkers implements QueueWorkers {
|
|
|
19
17
|
readonly configMappings: QueueConfigMapping;
|
|
20
18
|
private workers;
|
|
21
19
|
private queueEvents;
|
|
22
|
-
|
|
20
|
+
private runJob?;
|
|
21
|
+
private logger?;
|
|
22
|
+
constructor(redisConnectionOptions: ConnectionOptions);
|
|
23
|
+
setJobRunner(runJob: (params: {
|
|
24
|
+
job: QueueJob;
|
|
25
|
+
updateProgress?: (progress: number | string | object) => Promise<void>;
|
|
26
|
+
}) => Promise<void>, logger: Logger): void;
|
|
23
27
|
/**
|
|
24
28
|
* Scan state and register all compatible processors
|
|
25
29
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Worker, } from 'bullmq';
|
|
2
|
-
import {
|
|
2
|
+
import { registerQueueWorkers, QueueJobFailedError, QueueJobDiscardedError, } from '@pikku/core/queue';
|
|
3
3
|
import { mapBullJobToQueueJob } from './utils.js';
|
|
4
4
|
export const mapPikkuWorkerToBull = (workerConfig) => {
|
|
5
5
|
const workerOptions = {};
|
|
@@ -37,8 +37,6 @@ export const mapPikkuWorkerToBull = (workerConfig) => {
|
|
|
37
37
|
*/
|
|
38
38
|
export class BullQueueWorkers {
|
|
39
39
|
redisConnectionOptions;
|
|
40
|
-
singletonServices;
|
|
41
|
-
createWireServices;
|
|
42
40
|
name = 'bull';
|
|
43
41
|
supportsResults = true;
|
|
44
42
|
/**
|
|
@@ -101,21 +99,26 @@ export class BullQueueWorkers {
|
|
|
101
99
|
};
|
|
102
100
|
workers = new Map();
|
|
103
101
|
queueEvents = new Map();
|
|
104
|
-
|
|
102
|
+
runJob;
|
|
103
|
+
logger;
|
|
104
|
+
constructor(redisConnectionOptions) {
|
|
105
105
|
this.redisConnectionOptions = redisConnectionOptions;
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
}
|
|
107
|
+
setJobRunner(runJob, logger) {
|
|
108
|
+
this.runJob = runJob;
|
|
109
|
+
this.logger = logger;
|
|
108
110
|
}
|
|
109
111
|
/**
|
|
110
112
|
* Scan state and register all compatible processors
|
|
111
113
|
*/
|
|
112
114
|
async registerQueues() {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
if (!this.runJob || !this.logger) {
|
|
116
|
+
throw new Error('BullQueueWorkers requires setJobRunner() before registerQueues()');
|
|
117
|
+
}
|
|
118
|
+
return await registerQueueWorkers(this.configMappings, this.logger, async (queueName, processor) => {
|
|
119
|
+
const worker = new Worker(processor.name, async (job) => {
|
|
115
120
|
try {
|
|
116
|
-
return await
|
|
117
|
-
singletonServices: this.singletonServices,
|
|
118
|
-
createWireServices: this.createWireServices,
|
|
121
|
+
return await this.runJob({
|
|
119
122
|
job: await mapBullJobToQueueJob(job, this.redisConnectionOptions, this.queueEvents),
|
|
120
123
|
updateProgress: async (progress) => {
|
|
121
124
|
await job.updateProgress(progress);
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { ConnectionOptions } from 'bullmq';
|
|
2
|
-
import { SchedulerService, ScheduledTaskInfo, ScheduledTaskSummary, CoreUserSession } from '@pikku/core';
|
|
3
|
-
/**
|
|
4
|
-
* BullMQ scheduler service implementation
|
|
5
|
-
* Uses BullMQ's delayed jobs for one-time execution
|
|
6
|
-
*/
|
|
2
|
+
import { SchedulerService, SchedulerRuntimeHandlers, ScheduledTaskInfo, ScheduledTaskSummary, CoreUserSession } from '@pikku/core';
|
|
7
3
|
export declare class BullSchedulerService extends SchedulerService {
|
|
4
|
+
private redisConnectionOptions;
|
|
8
5
|
private queue;
|
|
6
|
+
private recurringQueue;
|
|
7
|
+
private recurringWorker?;
|
|
8
|
+
private repeatJobKeys;
|
|
9
|
+
private handlers?;
|
|
9
10
|
constructor(redisConnectionOptions: ConnectionOptions);
|
|
11
|
+
/**
|
|
12
|
+
* Set services needed for processing recurring tasks.
|
|
13
|
+
* Must be called before start() since the scheduler is typically
|
|
14
|
+
* created before singletonServices are available.
|
|
15
|
+
*/
|
|
16
|
+
setServices(handlers: SchedulerRuntimeHandlers): void;
|
|
10
17
|
/**
|
|
11
18
|
* Initialize - wait for queue to be ready
|
|
12
19
|
*/
|
|
@@ -32,4 +39,13 @@ export declare class BullSchedulerService extends SchedulerService {
|
|
|
32
39
|
* Close the queue connection
|
|
33
40
|
*/
|
|
34
41
|
close(): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Start recurring scheduled tasks.
|
|
44
|
+
* Creates a BullMQ Worker to process repeat jobs via runScheduledTask.
|
|
45
|
+
*/
|
|
46
|
+
start(): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Stop recurring scheduled tasks by removing repeat jobs.
|
|
49
|
+
*/
|
|
50
|
+
stop(): Promise<void>;
|
|
35
51
|
}
|
|
@@ -1,16 +1,31 @@
|
|
|
1
|
-
import { Queue } from 'bullmq';
|
|
1
|
+
import { Queue, Worker } from 'bullmq';
|
|
2
2
|
import { SchedulerService, parseDurationString, } from '@pikku/core';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* Uses BullMQ's delayed jobs for one-time execution
|
|
6
|
-
*/
|
|
3
|
+
import { getScheduledTasks } from '@pikku/core/scheduler';
|
|
4
|
+
const RECURRING_QUEUE_NAME = 'pikku-recurring-scheduled-task';
|
|
7
5
|
export class BullSchedulerService extends SchedulerService {
|
|
6
|
+
redisConnectionOptions;
|
|
8
7
|
queue;
|
|
8
|
+
recurringQueue;
|
|
9
|
+
recurringWorker;
|
|
10
|
+
repeatJobKeys = [];
|
|
11
|
+
handlers;
|
|
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
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Set services needed for processing recurring tasks.
|
|
24
|
+
* Must be called before start() since the scheduler is typically
|
|
25
|
+
* created before singletonServices are available.
|
|
26
|
+
*/
|
|
27
|
+
setServices(handlers) {
|
|
28
|
+
this.handlers = handlers;
|
|
14
29
|
}
|
|
15
30
|
/**
|
|
16
31
|
* Initialize - wait for queue to be ready
|
|
@@ -95,6 +110,51 @@ export class BullSchedulerService extends SchedulerService {
|
|
|
95
110
|
* Close the queue connection
|
|
96
111
|
*/
|
|
97
112
|
async close() {
|
|
113
|
+
if (this.recurringWorker) {
|
|
114
|
+
await this.recurringWorker.close();
|
|
115
|
+
}
|
|
116
|
+
await this.recurringQueue.close();
|
|
98
117
|
await this.queue.close();
|
|
99
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Start recurring scheduled tasks.
|
|
121
|
+
* Creates a BullMQ Worker to process repeat jobs via runScheduledTask.
|
|
122
|
+
*/
|
|
123
|
+
async start() {
|
|
124
|
+
if (!this.handlers) {
|
|
125
|
+
throw new Error('BullSchedulerService requires setServices() before start()');
|
|
126
|
+
}
|
|
127
|
+
const scheduledTasks = getScheduledTasks();
|
|
128
|
+
// Create a worker to process recurring scheduled task jobs
|
|
129
|
+
this.recurringWorker = new Worker(RECURRING_QUEUE_NAME, async (job) => {
|
|
130
|
+
const { rpcName } = job.data;
|
|
131
|
+
this.handlers.logger.info(`Running scheduled task: ${rpcName}`);
|
|
132
|
+
await this.handlers.runScheduledTask(rpcName);
|
|
133
|
+
}, { connection: this.redisConnectionOptions });
|
|
134
|
+
this.recurringWorker.on('error', (err) => {
|
|
135
|
+
this.handlers.logger.error(`Recurring task worker error: ${err}`);
|
|
136
|
+
});
|
|
137
|
+
for (const [name, task] of scheduledTasks) {
|
|
138
|
+
const job = await this.recurringQueue.add(name, { rpcName: name }, {
|
|
139
|
+
repeat: { pattern: task.schedule },
|
|
140
|
+
jobId: `recurring:${name}`,
|
|
141
|
+
});
|
|
142
|
+
if (job.repeatJobKey) {
|
|
143
|
+
this.repeatJobKeys.push(job.repeatJobKey);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Stop recurring scheduled tasks by removing repeat jobs.
|
|
149
|
+
*/
|
|
150
|
+
async stop() {
|
|
151
|
+
for (const key of this.repeatJobKeys) {
|
|
152
|
+
await this.recurringQueue.removeRepeatableByKey(key);
|
|
153
|
+
}
|
|
154
|
+
this.repeatJobKeys = [];
|
|
155
|
+
if (this.recurringWorker) {
|
|
156
|
+
await this.recurringWorker.close();
|
|
157
|
+
this.recurringWorker = undefined;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
100
160
|
}
|
|
@@ -2,7 +2,7 @@ import { 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 {
|
|
5
|
+
import type { CoreSingletonServices, CreateWireServices } from '@pikku/core';
|
|
6
6
|
/**
|
|
7
7
|
* Factory class for BullMQ services
|
|
8
8
|
* Manages Redis connection options shared across queue, worker, and scheduler services
|
|
@@ -24,13 +24,18 @@ export declare class BullServiceFactory {
|
|
|
24
24
|
/**
|
|
25
25
|
* Get the queue workers for processing jobs
|
|
26
26
|
*/
|
|
27
|
-
getQueueWorkers(singletonServices: CoreSingletonServices, createWireServices?: CreateWireServices
|
|
27
|
+
getQueueWorkers(singletonServices: CoreSingletonServices, createWireServices?: CreateWireServices): BullQueueWorkers;
|
|
28
28
|
/**
|
|
29
29
|
* Get the scheduler service for managing delayed tasks
|
|
30
30
|
*/
|
|
31
31
|
getSchedulerService(): BullSchedulerService;
|
|
32
|
+
setSchedulerRuntime(singletonServices: CoreSingletonServices, createWireServices?: CreateWireServices): void;
|
|
32
33
|
/**
|
|
33
34
|
* Close all services
|
|
34
35
|
*/
|
|
35
36
|
close(): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Alias for close() - used by stopSingletonServices
|
|
39
|
+
*/
|
|
40
|
+
stop(): Promise<void>;
|
|
36
41
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { BullQueueService } from './bull-queue-service.js';
|
|
2
2
|
import { BullQueueWorkers } from './bull-queue-worker.js';
|
|
3
3
|
import { BullSchedulerService } from './bull-scheduler-service.js';
|
|
4
|
+
import { createQueueJobRunner } from '@pikku/core/queue';
|
|
5
|
+
import { createSchedulerRuntimeHandlers } from '@pikku/core/scheduler';
|
|
4
6
|
/**
|
|
5
7
|
* Factory class for BullMQ services
|
|
6
8
|
* Manages Redis connection options shared across queue, worker, and scheduler services
|
|
@@ -33,8 +35,9 @@ export class BullServiceFactory {
|
|
|
33
35
|
*/
|
|
34
36
|
getQueueWorkers(singletonServices, createWireServices) {
|
|
35
37
|
if (!this.queueWorkers) {
|
|
36
|
-
this.queueWorkers = new BullQueueWorkers(this.redisConnectionOptions
|
|
38
|
+
this.queueWorkers = new BullQueueWorkers(this.redisConnectionOptions);
|
|
37
39
|
}
|
|
40
|
+
this.queueWorkers.setJobRunner(createQueueJobRunner({ singletonServices, createWireServices }), singletonServices.logger);
|
|
38
41
|
return this.queueWorkers;
|
|
39
42
|
}
|
|
40
43
|
/**
|
|
@@ -46,6 +49,12 @@ export class BullServiceFactory {
|
|
|
46
49
|
}
|
|
47
50
|
return this.schedulerService;
|
|
48
51
|
}
|
|
52
|
+
setSchedulerRuntime(singletonServices, createWireServices) {
|
|
53
|
+
this.getSchedulerService().setServices(createSchedulerRuntimeHandlers({
|
|
54
|
+
singletonServices,
|
|
55
|
+
createWireServices,
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
49
58
|
/**
|
|
50
59
|
* Close all services
|
|
51
60
|
*/
|
|
@@ -62,4 +71,10 @@ export class BullServiceFactory {
|
|
|
62
71
|
}
|
|
63
72
|
await Promise.all(closePromises);
|
|
64
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Alias for close() - used by stopSingletonServices
|
|
76
|
+
*/
|
|
77
|
+
async stop() {
|
|
78
|
+
await this.close();
|
|
79
|
+
}
|
|
65
80
|
}
|
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.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.
|
|
16
|
+
"@pikku/core": "^0.12.0"
|
|
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"
|
|
@@ -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
|
@@ -9,18 +9,14 @@ import type {
|
|
|
9
9
|
PikkuWorkerConfig,
|
|
10
10
|
QueueConfigMapping,
|
|
11
11
|
ConfigValidationResult,
|
|
12
|
+
QueueJob,
|
|
12
13
|
} from '@pikku/core/queue'
|
|
13
14
|
import {
|
|
14
|
-
runQueueJob,
|
|
15
15
|
registerQueueWorkers,
|
|
16
16
|
QueueJobFailedError,
|
|
17
17
|
QueueJobDiscardedError,
|
|
18
18
|
} from '@pikku/core/queue'
|
|
19
|
-
import type {
|
|
20
|
-
CoreServices,
|
|
21
|
-
CoreSingletonServices,
|
|
22
|
-
CreateWireServices,
|
|
23
|
-
} from '@pikku/core'
|
|
19
|
+
import type { Logger } from '@pikku/core/services'
|
|
24
20
|
import { mapBullJobToQueueJob } from './utils.js'
|
|
25
21
|
|
|
26
22
|
export const mapPikkuWorkerToBull = (
|
|
@@ -141,32 +137,46 @@ export class BullQueueWorkers implements QueueWorkers {
|
|
|
141
137
|
|
|
142
138
|
private workers = new Map<string, Bull.Worker>()
|
|
143
139
|
private queueEvents = new Map<string, QueueEvents>()
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
140
|
+
private runJob?:
|
|
141
|
+
| ((params: {
|
|
142
|
+
job: QueueJob
|
|
143
|
+
updateProgress?: (progress: number | string | object) => Promise<void>
|
|
144
|
+
}) => Promise<void>)
|
|
145
|
+
| undefined
|
|
146
|
+
private logger?: Logger
|
|
147
|
+
|
|
148
|
+
constructor(private redisConnectionOptions: ConnectionOptions) {}
|
|
149
|
+
|
|
150
|
+
setJobRunner(
|
|
151
|
+
runJob: (params: {
|
|
152
|
+
job: QueueJob
|
|
153
|
+
updateProgress?: (progress: number | string | object) => Promise<void>
|
|
154
|
+
}) => Promise<void>,
|
|
155
|
+
logger: Logger
|
|
156
|
+
): void {
|
|
157
|
+
this.runJob = runJob
|
|
158
|
+
this.logger = logger
|
|
159
|
+
}
|
|
154
160
|
|
|
155
161
|
/**
|
|
156
162
|
* Scan state and register all compatible processors
|
|
157
163
|
*/
|
|
158
164
|
async registerQueues(): Promise<Record<string, ConfigValidationResult[]>> {
|
|
165
|
+
if (!this.runJob || !this.logger) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
'BullQueueWorkers requires setJobRunner() before registerQueues()'
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
159
171
|
return await registerQueueWorkers(
|
|
160
172
|
this.configMappings,
|
|
161
|
-
this.
|
|
173
|
+
this.logger,
|
|
162
174
|
async (queueName, processor) => {
|
|
163
175
|
const worker = new Worker(
|
|
164
|
-
processor.
|
|
176
|
+
processor.name,
|
|
165
177
|
async (job: Bull.Job) => {
|
|
166
178
|
try {
|
|
167
|
-
return await
|
|
168
|
-
singletonServices: this.singletonServices,
|
|
169
|
-
createWireServices: this.createWireServices,
|
|
179
|
+
return await this.runJob!({
|
|
170
180
|
job: await mapBullJobToQueueJob(
|
|
171
181
|
job,
|
|
172
182
|
this.redisConnectionOptions,
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { ConnectionOptions, Queue } from 'bullmq'
|
|
1
|
+
import { ConnectionOptions, Queue, Worker } from 'bullmq'
|
|
2
2
|
import {
|
|
3
3
|
SchedulerService,
|
|
4
|
+
SchedulerRuntimeHandlers,
|
|
4
5
|
ScheduledTaskInfo,
|
|
5
6
|
ScheduledTaskSummary,
|
|
6
7
|
CoreUserSession,
|
|
7
8
|
parseDurationString,
|
|
8
9
|
} from '@pikku/core'
|
|
10
|
+
import { getScheduledTasks } from '@pikku/core/scheduler'
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Data stored in scheduled job
|
|
@@ -16,18 +18,32 @@ 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[] = []
|
|
28
|
+
private handlers?: SchedulerRuntimeHandlers
|
|
25
29
|
|
|
26
|
-
constructor(redisConnectionOptions: ConnectionOptions) {
|
|
30
|
+
constructor(private redisConnectionOptions: ConnectionOptions) {
|
|
27
31
|
super()
|
|
28
32
|
this.queue = new Queue('pikku-remote-internal-rpc', {
|
|
29
33
|
connection: redisConnectionOptions,
|
|
30
34
|
})
|
|
35
|
+
this.recurringQueue = new Queue(RECURRING_QUEUE_NAME, {
|
|
36
|
+
connection: redisConnectionOptions,
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Set services needed for processing recurring tasks.
|
|
42
|
+
* Must be called before start() since the scheduler is typically
|
|
43
|
+
* created before singletonServices are available.
|
|
44
|
+
*/
|
|
45
|
+
setServices(handlers: SchedulerRuntimeHandlers): void {
|
|
46
|
+
this.handlers = handlers
|
|
31
47
|
}
|
|
32
48
|
|
|
33
49
|
/**
|
|
@@ -134,6 +150,66 @@ export class BullSchedulerService extends SchedulerService {
|
|
|
134
150
|
* Close the queue connection
|
|
135
151
|
*/
|
|
136
152
|
async close(): Promise<void> {
|
|
153
|
+
if (this.recurringWorker) {
|
|
154
|
+
await this.recurringWorker.close()
|
|
155
|
+
}
|
|
156
|
+
await this.recurringQueue.close()
|
|
137
157
|
await this.queue.close()
|
|
138
158
|
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Start recurring scheduled tasks.
|
|
162
|
+
* Creates a BullMQ Worker to process repeat jobs via runScheduledTask.
|
|
163
|
+
*/
|
|
164
|
+
async start(): Promise<void> {
|
|
165
|
+
if (!this.handlers) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
'BullSchedulerService requires setServices() before start()'
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const scheduledTasks = getScheduledTasks()
|
|
172
|
+
|
|
173
|
+
// Create a worker to process recurring scheduled task jobs
|
|
174
|
+
this.recurringWorker = new Worker(
|
|
175
|
+
RECURRING_QUEUE_NAME,
|
|
176
|
+
async (job) => {
|
|
177
|
+
const { rpcName } = job.data as ScheduledJobData
|
|
178
|
+
this.handlers!.logger.info(`Running scheduled task: ${rpcName}`)
|
|
179
|
+
await this.handlers!.runScheduledTask(rpcName)
|
|
180
|
+
},
|
|
181
|
+
{ connection: this.redisConnectionOptions }
|
|
182
|
+
)
|
|
183
|
+
this.recurringWorker.on('error', (err) => {
|
|
184
|
+
this.handlers!.logger.error(`Recurring task worker error: ${err}`)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
for (const [name, task] of scheduledTasks) {
|
|
188
|
+
const job = await this.recurringQueue.add(
|
|
189
|
+
name,
|
|
190
|
+
{ rpcName: name } as ScheduledJobData,
|
|
191
|
+
{
|
|
192
|
+
repeat: { pattern: task.schedule },
|
|
193
|
+
jobId: `recurring:${name}`,
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
if (job.repeatJobKey) {
|
|
197
|
+
this.repeatJobKeys.push(job.repeatJobKey)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Stop recurring scheduled tasks by removing repeat jobs.
|
|
204
|
+
*/
|
|
205
|
+
async stop(): Promise<void> {
|
|
206
|
+
for (const key of this.repeatJobKeys) {
|
|
207
|
+
await this.recurringQueue.removeRepeatableByKey(key)
|
|
208
|
+
}
|
|
209
|
+
this.repeatJobKeys = []
|
|
210
|
+
if (this.recurringWorker) {
|
|
211
|
+
await this.recurringWorker.close()
|
|
212
|
+
this.recurringWorker = undefined
|
|
213
|
+
}
|
|
214
|
+
}
|
|
139
215
|
}
|
|
@@ -2,11 +2,9 @@ import { 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 {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
CreateWireServices,
|
|
9
|
-
} from '@pikku/core'
|
|
5
|
+
import type { CoreSingletonServices, CreateWireServices } from '@pikku/core'
|
|
6
|
+
import { createQueueJobRunner } from '@pikku/core/queue'
|
|
7
|
+
import { createSchedulerRuntimeHandlers } from '@pikku/core/scheduler'
|
|
10
8
|
|
|
11
9
|
/**
|
|
12
10
|
* Factory class for BullMQ services
|
|
@@ -41,19 +39,15 @@ export class BullServiceFactory {
|
|
|
41
39
|
*/
|
|
42
40
|
getQueueWorkers(
|
|
43
41
|
singletonServices: CoreSingletonServices,
|
|
44
|
-
createWireServices?: CreateWireServices
|
|
45
|
-
CoreSingletonServices,
|
|
46
|
-
CoreServices,
|
|
47
|
-
any
|
|
48
|
-
>
|
|
42
|
+
createWireServices?: CreateWireServices
|
|
49
43
|
): BullQueueWorkers {
|
|
50
44
|
if (!this.queueWorkers) {
|
|
51
|
-
this.queueWorkers = new BullQueueWorkers(
|
|
52
|
-
this.redisConnectionOptions,
|
|
53
|
-
singletonServices,
|
|
54
|
-
createWireServices
|
|
55
|
-
)
|
|
45
|
+
this.queueWorkers = new BullQueueWorkers(this.redisConnectionOptions)
|
|
56
46
|
}
|
|
47
|
+
this.queueWorkers.setJobRunner(
|
|
48
|
+
createQueueJobRunner({ singletonServices, createWireServices }),
|
|
49
|
+
singletonServices.logger
|
|
50
|
+
)
|
|
57
51
|
return this.queueWorkers
|
|
58
52
|
}
|
|
59
53
|
|
|
@@ -69,6 +63,18 @@ export class BullServiceFactory {
|
|
|
69
63
|
return this.schedulerService
|
|
70
64
|
}
|
|
71
65
|
|
|
66
|
+
setSchedulerRuntime(
|
|
67
|
+
singletonServices: CoreSingletonServices,
|
|
68
|
+
createWireServices?: CreateWireServices
|
|
69
|
+
): void {
|
|
70
|
+
this.getSchedulerService().setServices(
|
|
71
|
+
createSchedulerRuntimeHandlers({
|
|
72
|
+
singletonServices,
|
|
73
|
+
createWireServices,
|
|
74
|
+
})
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
72
78
|
/**
|
|
73
79
|
* Close all services
|
|
74
80
|
*/
|
|
@@ -89,4 +95,11 @@ export class BullServiceFactory {
|
|
|
89
95
|
|
|
90
96
|
await Promise.all(closePromises)
|
|
91
97
|
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Alias for close() - used by stopSingletonServices
|
|
101
|
+
*/
|
|
102
|
+
async stop(): Promise<void> {
|
|
103
|
+
await this.close()
|
|
104
|
+
}
|
|
92
105
|
}
|
package/src/utils.ts
CHANGED
|
@@ -36,13 +36,14 @@ export const mapBullJobToQueueJob = async <In, Out>(
|
|
|
36
36
|
result: bullJob.returnvalue as Out,
|
|
37
37
|
status: async () => mapBullStateToStatus(await bullJob.getState()),
|
|
38
38
|
waitForCompletion: async (ttl?: number): Promise<Out> => {
|
|
39
|
-
let queueEvents = queueEventsMap.get(bullJob.
|
|
39
|
+
let queueEvents = queueEventsMap.get(bullJob.queueName)
|
|
40
40
|
if (!queueEvents) {
|
|
41
41
|
queueEvents = new QueueEvents(
|
|
42
|
-
bullJob.
|
|
42
|
+
bullJob.queueName,
|
|
43
43
|
redisConnection ? { connection: redisConnection } : undefined
|
|
44
44
|
)
|
|
45
|
-
queueEventsMap.set(bullJob.
|
|
45
|
+
queueEventsMap.set(bullJob.queueName, queueEvents)
|
|
46
|
+
await queueEvents.waitUntilReady()
|
|
46
47
|
}
|
|
47
48
|
return await bullJob.waitUntilFinished(queueEvents, ttl)
|
|
48
49
|
},
|