@pikku/queue-pg-boss 0.11.2 → 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 +6 -2
- package/dist/pg-boss-queue-worker.d.ts +9 -5
- package/dist/pg-boss-queue-worker.js +16 -16
- package/dist/pg-boss-scheduler-service.d.ts +20 -2
- package/dist/pg-boss-scheduler-service.js +47 -1
- package/dist/pg-boss-service-factory.d.ts +1 -0
- package/dist/pg-boss-service-factory.js +10 -1
- package/package.json +3 -3
- package/src/pg-boss-queue-worker.test.ts +15 -0
- package/src/pg-boss-queue-worker.ts +32 -19
- package/src/pg-boss-scheduler-service.ts +57 -1
- package/src/pg-boss-service-factory.ts +19 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## 0.12.0
|
|
2
|
+
|
|
3
|
+
### New Features
|
|
4
|
+
|
|
5
|
+
- `setServices()`, `start()`, `stop()` lifecycle methods on scheduler and queue workers
|
|
6
|
+
|
|
1
7
|
## 0.11.0
|
|
2
8
|
|
|
3
9
|
## 0.11.2
|
|
@@ -8,8 +14,6 @@
|
|
|
8
14
|
- Updated dependencies [db9c7bf]
|
|
9
15
|
- @pikku/core@0.11.2
|
|
10
16
|
|
|
11
|
-
## 0.11.2
|
|
12
|
-
|
|
13
17
|
### Fixes
|
|
14
18
|
|
|
15
19
|
- ddd87eaf: Simplify CreateWireServices type signature for custom Config compatibility
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { PgBoss, type WorkOptions } from 'pg-boss';
|
|
2
|
-
import type { QueueWorkers, PikkuWorkerConfig, QueueConfigMapping, ConfigValidationResult } from '@pikku/core/queue';
|
|
3
|
-
import {
|
|
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 mapPikkuWorkerToPgBoss: (workerConfig?: PikkuWorkerConfig) => WorkOptions;
|
|
5
5
|
/**
|
|
6
6
|
* pg-boss/PostgreSQL queue worker implementation
|
|
7
7
|
* Supports job results, retries, and PostgreSQL-based queuing
|
|
8
8
|
*/
|
|
9
9
|
export declare class PgBossQueueWorkers implements QueueWorkers {
|
|
10
|
-
private singletonServices;
|
|
11
|
-
private createWireServices?;
|
|
12
10
|
readonly name = "pg-boss";
|
|
13
11
|
readonly supportsResults = true;
|
|
14
12
|
/**
|
|
@@ -18,7 +16,13 @@ export declare class PgBossQueueWorkers implements QueueWorkers {
|
|
|
18
16
|
readonly configMappings: QueueConfigMapping;
|
|
19
17
|
private pgBoss;
|
|
20
18
|
private activeWorkers;
|
|
21
|
-
|
|
19
|
+
private runJob?;
|
|
20
|
+
private logger?;
|
|
21
|
+
constructor(pgBoss: PgBoss);
|
|
22
|
+
setJobRunner(runJob: (params: {
|
|
23
|
+
job: QueueJob;
|
|
24
|
+
updateProgress?: (progress: number | string | object) => Promise<void>;
|
|
25
|
+
}) => Promise<void>, logger: Logger): void;
|
|
22
26
|
/**
|
|
23
27
|
* Scan state and register all compatible processors
|
|
24
28
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { registerQueueWorkers, QueueJobFailedError, QueueJobDiscardedError, } from '@pikku/core/queue';
|
|
2
2
|
import { mapPgBossJobToQueueJob } from './utils.js';
|
|
3
3
|
export const mapPikkuWorkerToPgBoss = (workerConfig) => {
|
|
4
4
|
const workerOptions = {
|
|
@@ -17,8 +17,6 @@ export const mapPikkuWorkerToPgBoss = (workerConfig) => {
|
|
|
17
17
|
* Supports job results, retries, and PostgreSQL-based queuing
|
|
18
18
|
*/
|
|
19
19
|
export class PgBossQueueWorkers {
|
|
20
|
-
singletonServices;
|
|
21
|
-
createWireServices;
|
|
22
20
|
name = 'pg-boss';
|
|
23
21
|
supportsResults = true;
|
|
24
22
|
/**
|
|
@@ -85,34 +83,36 @@ export class PgBossQueueWorkers {
|
|
|
85
83
|
};
|
|
86
84
|
pgBoss;
|
|
87
85
|
activeWorkers = new Map();
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
runJob;
|
|
87
|
+
logger;
|
|
88
|
+
constructor(pgBoss) {
|
|
91
89
|
this.pgBoss = pgBoss;
|
|
92
90
|
}
|
|
91
|
+
setJobRunner(runJob, logger) {
|
|
92
|
+
this.runJob = runJob;
|
|
93
|
+
this.logger = logger;
|
|
94
|
+
}
|
|
93
95
|
/**
|
|
94
96
|
* Scan state and register all compatible processors
|
|
95
97
|
*/
|
|
96
98
|
async registerQueues() {
|
|
97
|
-
|
|
99
|
+
if (!this.runJob || !this.logger) {
|
|
100
|
+
throw new Error('PgBossQueueWorkers requires setJobRunner() before registerQueues()');
|
|
101
|
+
}
|
|
102
|
+
return await registerQueueWorkers(this.configMappings, this.logger, async (queueName, processor) => {
|
|
98
103
|
await this.pgBoss.createQueue(queueName);
|
|
99
|
-
const workerId = await this.pgBoss.work(processor.
|
|
104
|
+
const workerId = await this.pgBoss.work(processor.name, {
|
|
100
105
|
...mapPikkuWorkerToPgBoss(processor.config),
|
|
101
106
|
includeMetadata: true,
|
|
102
107
|
}, async (jobs) => {
|
|
103
|
-
if (!this.singletonServices) {
|
|
104
|
-
throw new Error('Singleton services not available');
|
|
105
|
-
}
|
|
106
108
|
if (!jobs || jobs.length === 0) {
|
|
107
|
-
this.
|
|
109
|
+
this.logger.warn(`No jobs received for queue ${queueName}`);
|
|
108
110
|
return;
|
|
109
111
|
}
|
|
110
112
|
// Process all jobs in parallel
|
|
111
113
|
await Promise.all(jobs.map(async (job) => {
|
|
112
114
|
try {
|
|
113
|
-
await
|
|
114
|
-
singletonServices: this.singletonServices,
|
|
115
|
-
createWireServices: this.createWireServices,
|
|
115
|
+
await this.runJob({
|
|
116
116
|
job: mapPgBossJobToQueueJob(job, this.pgBoss),
|
|
117
117
|
});
|
|
118
118
|
}
|
|
@@ -125,7 +125,7 @@ export class PgBossQueueWorkers {
|
|
|
125
125
|
}
|
|
126
126
|
else if (error instanceof QueueJobDiscardedError) {
|
|
127
127
|
// For pg-boss, complete the job successfully to discard it
|
|
128
|
-
this.
|
|
128
|
+
this.logger.info(`PgBoss job ${job.id} discarded: ${error.message}`);
|
|
129
129
|
await this.pgBoss.complete(queueName, job.id);
|
|
130
130
|
}
|
|
131
131
|
else {
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { PgBoss } from 'pg-boss';
|
|
2
|
-
import { SchedulerService, ScheduledTaskInfo, ScheduledTaskSummary, CoreUserSession } from '@pikku/core';
|
|
2
|
+
import { SchedulerService, SchedulerRuntimeHandlers, ScheduledTaskInfo, ScheduledTaskSummary, CoreUserSession } from '@pikku/core';
|
|
3
3
|
/**
|
|
4
4
|
* pg-boss scheduler service implementation
|
|
5
|
-
* Uses pg-boss's schedule() API to store recurring jobs in PostgreSQL
|
|
5
|
+
* Uses pg-boss's schedule() API to store recurring jobs in PostgreSQL.
|
|
6
|
+
* Handles both one-off delayed RPCs and recurring cron-scheduled tasks.
|
|
6
7
|
*/
|
|
7
8
|
export declare class PgBossSchedulerService extends SchedulerService {
|
|
8
9
|
private pgBoss;
|
|
10
|
+
private scheduledCronNames;
|
|
11
|
+
private handlers?;
|
|
9
12
|
constructor(pgBoss: PgBoss);
|
|
13
|
+
/**
|
|
14
|
+
* Set services needed for processing recurring tasks.
|
|
15
|
+
* Must be called before start() since the scheduler is typically
|
|
16
|
+
* created before singletonServices are available.
|
|
17
|
+
*/
|
|
18
|
+
setServices(handlers: SchedulerRuntimeHandlers): void;
|
|
10
19
|
/**
|
|
11
20
|
* Initialize - no-op as pg-boss is already initialized by factory
|
|
12
21
|
*/
|
|
@@ -32,4 +41,13 @@ export declare class PgBossSchedulerService extends SchedulerService {
|
|
|
32
41
|
* Close - no-op as pg-boss lifecycle is managed by factory
|
|
33
42
|
*/
|
|
34
43
|
close(): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Start recurring scheduled tasks.
|
|
46
|
+
* Registers a pg-boss worker to process recurring jobs via runScheduledTask.
|
|
47
|
+
*/
|
|
48
|
+
start(): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Stop recurring scheduled tasks by unscheduling them from pg-boss.
|
|
51
|
+
*/
|
|
52
|
+
stop(): Promise<void>;
|
|
35
53
|
}
|
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
import { SchedulerService, parseDurationString, } from '@pikku/core';
|
|
2
|
+
import { getScheduledTasks } from '@pikku/core/scheduler';
|
|
2
3
|
/**
|
|
3
4
|
* pg-boss scheduler service implementation
|
|
4
|
-
* Uses pg-boss's schedule() API to store recurring jobs in PostgreSQL
|
|
5
|
+
* Uses pg-boss's schedule() API to store recurring jobs in PostgreSQL.
|
|
6
|
+
* Handles both one-off delayed RPCs and recurring cron-scheduled tasks.
|
|
5
7
|
*/
|
|
6
8
|
export class PgBossSchedulerService extends SchedulerService {
|
|
7
9
|
pgBoss;
|
|
10
|
+
scheduledCronNames = [];
|
|
11
|
+
handlers;
|
|
8
12
|
constructor(pgBoss) {
|
|
9
13
|
super();
|
|
10
14
|
this.pgBoss = pgBoss;
|
|
11
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Set services needed for processing recurring tasks.
|
|
18
|
+
* Must be called before start() since the scheduler is typically
|
|
19
|
+
* created before singletonServices are available.
|
|
20
|
+
*/
|
|
21
|
+
setServices(handlers) {
|
|
22
|
+
this.handlers = handlers;
|
|
23
|
+
}
|
|
12
24
|
/**
|
|
13
25
|
* Initialize - no-op as pg-boss is already initialized by factory
|
|
14
26
|
*/
|
|
@@ -77,4 +89,38 @@ export class PgBossSchedulerService extends SchedulerService {
|
|
|
77
89
|
async close() {
|
|
78
90
|
// No-op - pg-boss lifecycle is managed by factory
|
|
79
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Start recurring scheduled tasks.
|
|
94
|
+
* Registers a pg-boss worker to process recurring jobs via runScheduledTask.
|
|
95
|
+
*/
|
|
96
|
+
async start() {
|
|
97
|
+
if (!this.handlers) {
|
|
98
|
+
throw new Error('PgBossSchedulerService requires setServices() before start()');
|
|
99
|
+
}
|
|
100
|
+
const scheduledTasks = getScheduledTasks();
|
|
101
|
+
for (const [name, task] of scheduledTasks) {
|
|
102
|
+
const cronName = `pikku-recurring-scheduled-task:${name}`;
|
|
103
|
+
await this.pgBoss.schedule(cronName, task.schedule, {
|
|
104
|
+
rpcName: name,
|
|
105
|
+
});
|
|
106
|
+
this.scheduledCronNames.push(cronName);
|
|
107
|
+
// Register a worker to process the scheduled jobs
|
|
108
|
+
await this.pgBoss.work(cronName, async (jobs) => {
|
|
109
|
+
for (const job of jobs) {
|
|
110
|
+
const { rpcName } = job.data;
|
|
111
|
+
this.handlers.logger.info(`Running scheduled task: ${rpcName}`);
|
|
112
|
+
await this.handlers.runScheduledTask(rpcName);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Stop recurring scheduled tasks by unscheduling them from pg-boss.
|
|
119
|
+
*/
|
|
120
|
+
async stop() {
|
|
121
|
+
for (const cronName of this.scheduledCronNames) {
|
|
122
|
+
await this.pgBoss.unschedule(cronName);
|
|
123
|
+
}
|
|
124
|
+
this.scheduledCronNames = [];
|
|
125
|
+
}
|
|
80
126
|
}
|
|
@@ -30,6 +30,7 @@ export declare class PgBossServiceFactory {
|
|
|
30
30
|
* Get the scheduler service for managing recurring tasks
|
|
31
31
|
*/
|
|
32
32
|
getSchedulerService(): PgBossSchedulerService;
|
|
33
|
+
setSchedulerRuntime(singletonServices: CoreSingletonServices, createWireServices?: CreateWireServices): void;
|
|
33
34
|
/**
|
|
34
35
|
* Close all services and the pg-boss connection
|
|
35
36
|
*/
|
|
@@ -2,6 +2,8 @@ import { PgBoss } from 'pg-boss';
|
|
|
2
2
|
import { PgBossQueueService } from './pg-boss-queue-service.js';
|
|
3
3
|
import { PgBossQueueWorkers } from './pg-boss-queue-worker.js';
|
|
4
4
|
import { PgBossSchedulerService } from './pg-boss-scheduler-service.js';
|
|
5
|
+
import { createQueueJobRunner } from '@pikku/core/queue';
|
|
6
|
+
import { createSchedulerRuntimeHandlers } from '@pikku/core/scheduler';
|
|
5
7
|
/**
|
|
6
8
|
* Factory class for pg-boss services
|
|
7
9
|
* Manages a single PgBoss instance shared across queue, worker, and scheduler services
|
|
@@ -46,8 +48,9 @@ export class PgBossServiceFactory {
|
|
|
46
48
|
*/
|
|
47
49
|
getQueueWorkers(singletonServices, createWireServices) {
|
|
48
50
|
if (!this.queueWorkers) {
|
|
49
|
-
this.queueWorkers = new PgBossQueueWorkers(this.pgBoss
|
|
51
|
+
this.queueWorkers = new PgBossQueueWorkers(this.pgBoss);
|
|
50
52
|
}
|
|
53
|
+
this.queueWorkers.setJobRunner(createQueueJobRunner({ singletonServices, createWireServices }), singletonServices.logger);
|
|
51
54
|
return this.queueWorkers;
|
|
52
55
|
}
|
|
53
56
|
/**
|
|
@@ -59,6 +62,12 @@ export class PgBossServiceFactory {
|
|
|
59
62
|
}
|
|
60
63
|
return this.schedulerService;
|
|
61
64
|
}
|
|
65
|
+
setSchedulerRuntime(singletonServices, createWireServices) {
|
|
66
|
+
this.getSchedulerService().setServices(createSchedulerRuntimeHandlers({
|
|
67
|
+
singletonServices,
|
|
68
|
+
createWireServices,
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
62
71
|
/**
|
|
63
72
|
* Close all services and the pg-boss connection
|
|
64
73
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/queue-pg-boss",
|
|
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,10 +13,10 @@
|
|
|
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
|
-
"pg-boss": "^12.
|
|
19
|
+
"pg-boss": "^12.9.0"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"typescript": "^5.9"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import { PgBossQueueWorkers } from './pg-boss-queue-worker.js'
|
|
5
|
+
|
|
6
|
+
describe('PgBossQueueWorkers', () => {
|
|
7
|
+
test('requires setJobRunner before registerQueues', async () => {
|
|
8
|
+
const workers = new PgBossQueueWorkers({} as any)
|
|
9
|
+
|
|
10
|
+
await assert.rejects(() => workers.registerQueues(), {
|
|
11
|
+
message:
|
|
12
|
+
'PgBossQueueWorkers requires setJobRunner() before registerQueues()',
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
})
|
|
@@ -4,14 +4,14 @@ import type {
|
|
|
4
4
|
PikkuWorkerConfig,
|
|
5
5
|
QueueConfigMapping,
|
|
6
6
|
ConfigValidationResult,
|
|
7
|
+
QueueJob,
|
|
7
8
|
} from '@pikku/core/queue'
|
|
8
9
|
import {
|
|
9
|
-
runQueueJob,
|
|
10
10
|
registerQueueWorkers,
|
|
11
11
|
QueueJobFailedError,
|
|
12
12
|
QueueJobDiscardedError,
|
|
13
13
|
} from '@pikku/core/queue'
|
|
14
|
-
import {
|
|
14
|
+
import type { Logger } from '@pikku/core/services'
|
|
15
15
|
import { mapPgBossJobToQueueJob } from './utils.js'
|
|
16
16
|
|
|
17
17
|
export const mapPikkuWorkerToPgBoss = (
|
|
@@ -120,47 +120,60 @@ export class PgBossQueueWorkers implements QueueWorkers {
|
|
|
120
120
|
|
|
121
121
|
private pgBoss: PgBoss
|
|
122
122
|
private activeWorkers = new Map<string, string>()
|
|
123
|
+
private runJob?:
|
|
124
|
+
| ((params: {
|
|
125
|
+
job: QueueJob
|
|
126
|
+
updateProgress?: (progress: number | string | object) => Promise<void>
|
|
127
|
+
}) => Promise<void>)
|
|
128
|
+
| undefined
|
|
129
|
+
private logger?: Logger
|
|
123
130
|
|
|
124
|
-
constructor(
|
|
125
|
-
pgBoss: PgBoss,
|
|
126
|
-
private singletonServices: CoreSingletonServices,
|
|
127
|
-
private createWireServices?: CreateWireServices
|
|
128
|
-
) {
|
|
131
|
+
constructor(pgBoss: PgBoss) {
|
|
129
132
|
this.pgBoss = pgBoss
|
|
130
133
|
}
|
|
131
134
|
|
|
135
|
+
setJobRunner(
|
|
136
|
+
runJob: (params: {
|
|
137
|
+
job: QueueJob
|
|
138
|
+
updateProgress?: (progress: number | string | object) => Promise<void>
|
|
139
|
+
}) => Promise<void>,
|
|
140
|
+
logger: Logger
|
|
141
|
+
): void {
|
|
142
|
+
this.runJob = runJob
|
|
143
|
+
this.logger = logger
|
|
144
|
+
}
|
|
145
|
+
|
|
132
146
|
/**
|
|
133
147
|
* Scan state and register all compatible processors
|
|
134
148
|
*/
|
|
135
149
|
async registerQueues(): Promise<Record<string, ConfigValidationResult[]>> {
|
|
150
|
+
if (!this.runJob || !this.logger) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
'PgBossQueueWorkers requires setJobRunner() before registerQueues()'
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
136
156
|
return await registerQueueWorkers(
|
|
137
157
|
this.configMappings,
|
|
138
|
-
this.
|
|
158
|
+
this.logger,
|
|
139
159
|
async (queueName, processor) => {
|
|
140
160
|
await this.pgBoss.createQueue(queueName)
|
|
141
161
|
const workerId = await this.pgBoss.work<any>(
|
|
142
|
-
processor.
|
|
162
|
+
processor.name,
|
|
143
163
|
{
|
|
144
164
|
...mapPikkuWorkerToPgBoss(processor.config),
|
|
145
165
|
includeMetadata: true,
|
|
146
166
|
},
|
|
147
167
|
async (jobs) => {
|
|
148
|
-
if (!this.singletonServices) {
|
|
149
|
-
throw new Error('Singleton services not available')
|
|
150
|
-
}
|
|
151
168
|
if (!jobs || jobs.length === 0) {
|
|
152
|
-
this.
|
|
153
|
-
`No jobs received for queue ${queueName}`
|
|
154
|
-
)
|
|
169
|
+
this.logger!.warn(`No jobs received for queue ${queueName}`)
|
|
155
170
|
return
|
|
156
171
|
}
|
|
157
172
|
// Process all jobs in parallel
|
|
158
173
|
await Promise.all(
|
|
159
174
|
jobs.map(async (job) => {
|
|
160
175
|
try {
|
|
161
|
-
await
|
|
162
|
-
singletonServices: this.singletonServices,
|
|
163
|
-
createWireServices: this.createWireServices,
|
|
176
|
+
await this.runJob!({
|
|
164
177
|
job: mapPgBossJobToQueueJob(job, this.pgBoss),
|
|
165
178
|
})
|
|
166
179
|
} catch (error: unknown) {
|
|
@@ -171,7 +184,7 @@ export class PgBossQueueWorkers implements QueueWorkers {
|
|
|
171
184
|
})
|
|
172
185
|
} else if (error instanceof QueueJobDiscardedError) {
|
|
173
186
|
// For pg-boss, complete the job successfully to discard it
|
|
174
|
-
this.
|
|
187
|
+
this.logger!.info(
|
|
175
188
|
`PgBoss job ${job.id} discarded: ${error.message}`
|
|
176
189
|
)
|
|
177
190
|
await this.pgBoss.complete(queueName, job.id)
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { PgBoss } from 'pg-boss'
|
|
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
|
|
@@ -18,13 +20,26 @@ interface ScheduledJobData {
|
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* pg-boss scheduler service implementation
|
|
21
|
-
* Uses pg-boss's schedule() API to store recurring jobs in PostgreSQL
|
|
23
|
+
* Uses pg-boss's schedule() API to store recurring jobs in PostgreSQL.
|
|
24
|
+
* Handles both one-off delayed RPCs and recurring cron-scheduled tasks.
|
|
22
25
|
*/
|
|
23
26
|
export class PgBossSchedulerService extends SchedulerService {
|
|
27
|
+
private scheduledCronNames: string[] = []
|
|
28
|
+
private handlers?: SchedulerRuntimeHandlers
|
|
29
|
+
|
|
24
30
|
constructor(private pgBoss: PgBoss) {
|
|
25
31
|
super()
|
|
26
32
|
}
|
|
27
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Set services needed for processing recurring tasks.
|
|
36
|
+
* Must be called before start() since the scheduler is typically
|
|
37
|
+
* created before singletonServices are available.
|
|
38
|
+
*/
|
|
39
|
+
setServices(handlers: SchedulerRuntimeHandlers): void {
|
|
40
|
+
this.handlers = handlers
|
|
41
|
+
}
|
|
42
|
+
|
|
28
43
|
/**
|
|
29
44
|
* Initialize - no-op as pg-boss is already initialized by factory
|
|
30
45
|
*/
|
|
@@ -118,4 +133,45 @@ export class PgBossSchedulerService extends SchedulerService {
|
|
|
118
133
|
async close(): Promise<void> {
|
|
119
134
|
// No-op - pg-boss lifecycle is managed by factory
|
|
120
135
|
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Start recurring scheduled tasks.
|
|
139
|
+
* Registers a pg-boss worker to process recurring jobs via runScheduledTask.
|
|
140
|
+
*/
|
|
141
|
+
async start(): Promise<void> {
|
|
142
|
+
if (!this.handlers) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
'PgBossSchedulerService requires setServices() before start()'
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const scheduledTasks = getScheduledTasks()
|
|
149
|
+
|
|
150
|
+
for (const [name, task] of scheduledTasks) {
|
|
151
|
+
const cronName = `pikku-recurring-scheduled-task:${name}`
|
|
152
|
+
await this.pgBoss.schedule(cronName, task.schedule, {
|
|
153
|
+
rpcName: name,
|
|
154
|
+
} as ScheduledJobData)
|
|
155
|
+
this.scheduledCronNames.push(cronName)
|
|
156
|
+
|
|
157
|
+
// Register a worker to process the scheduled jobs
|
|
158
|
+
await this.pgBoss.work<ScheduledJobData>(cronName, async (jobs) => {
|
|
159
|
+
for (const job of jobs) {
|
|
160
|
+
const { rpcName } = job.data
|
|
161
|
+
this.handlers!.logger.info(`Running scheduled task: ${rpcName}`)
|
|
162
|
+
await this.handlers!.runScheduledTask(rpcName)
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Stop recurring scheduled tasks by unscheduling them from pg-boss.
|
|
170
|
+
*/
|
|
171
|
+
async stop(): Promise<void> {
|
|
172
|
+
for (const cronName of this.scheduledCronNames) {
|
|
173
|
+
await this.pgBoss.unschedule(cronName)
|
|
174
|
+
}
|
|
175
|
+
this.scheduledCronNames = []
|
|
176
|
+
}
|
|
121
177
|
}
|
|
@@ -3,6 +3,8 @@ import { PgBossQueueService } from './pg-boss-queue-service.js'
|
|
|
3
3
|
import { PgBossQueueWorkers } from './pg-boss-queue-worker.js'
|
|
4
4
|
import { PgBossSchedulerService } from './pg-boss-scheduler-service.js'
|
|
5
5
|
import type { CoreSingletonServices, CreateWireServices } from '@pikku/core'
|
|
6
|
+
import { createQueueJobRunner } from '@pikku/core/queue'
|
|
7
|
+
import { createSchedulerRuntimeHandlers } from '@pikku/core/scheduler'
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Factory class for pg-boss services
|
|
@@ -55,12 +57,12 @@ export class PgBossServiceFactory {
|
|
|
55
57
|
createWireServices?: CreateWireServices
|
|
56
58
|
): PgBossQueueWorkers {
|
|
57
59
|
if (!this.queueWorkers) {
|
|
58
|
-
this.queueWorkers = new PgBossQueueWorkers(
|
|
59
|
-
this.pgBoss,
|
|
60
|
-
singletonServices,
|
|
61
|
-
createWireServices
|
|
62
|
-
)
|
|
60
|
+
this.queueWorkers = new PgBossQueueWorkers(this.pgBoss)
|
|
63
61
|
}
|
|
62
|
+
this.queueWorkers.setJobRunner(
|
|
63
|
+
createQueueJobRunner({ singletonServices, createWireServices }),
|
|
64
|
+
singletonServices.logger
|
|
65
|
+
)
|
|
64
66
|
return this.queueWorkers
|
|
65
67
|
}
|
|
66
68
|
|
|
@@ -74,6 +76,18 @@ export class PgBossServiceFactory {
|
|
|
74
76
|
return this.schedulerService
|
|
75
77
|
}
|
|
76
78
|
|
|
79
|
+
setSchedulerRuntime(
|
|
80
|
+
singletonServices: CoreSingletonServices,
|
|
81
|
+
createWireServices?: CreateWireServices
|
|
82
|
+
): void {
|
|
83
|
+
this.getSchedulerService().setServices(
|
|
84
|
+
createSchedulerRuntimeHandlers({
|
|
85
|
+
singletonServices,
|
|
86
|
+
createWireServices,
|
|
87
|
+
})
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
77
91
|
/**
|
|
78
92
|
* Close all services and the pg-boss connection
|
|
79
93
|
*/
|