@rvoh/psychic-workers 0.3.1 → 0.4.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/dist/cjs/src/background/BaseBackgroundedModel.js +7 -4
- package/dist/cjs/src/background/BaseBackgroundedService.js +4 -2
- package/dist/cjs/src/background/index.js +29 -19
- package/dist/cjs/src/helpers/durationToSeconds.js +9 -0
- package/dist/esm/src/background/BaseBackgroundedModel.js +7 -4
- package/dist/esm/src/background/BaseBackgroundedService.js +4 -2
- package/dist/esm/src/background/index.js +29 -19
- package/dist/esm/src/helpers/durationToSeconds.js +6 -0
- package/dist/types/src/background/BaseBackgroundedModel.d.ts +3 -3
- package/dist/types/src/background/BaseBackgroundedService.d.ts +2 -2
- package/dist/types/src/background/index.d.ts +8 -5
- package/dist/types/src/helpers/durationToSeconds.d.ts +2 -0
- package/dist/types/src/psychic-app-workers/index.d.ts +2 -2
- package/dist/types/src/types/background.d.ts +14 -0
- package/package.json +1 -1
@@ -2,6 +2,7 @@
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
const dream_1 = require("@rvoh/dream");
|
4
4
|
const index_js_1 = require("./index.js");
|
5
|
+
const durationToSeconds_js_1 = require("../helpers/durationToSeconds.js");
|
5
6
|
class BaseBackgroundedModel extends dream_1.Dream {
|
6
7
|
/**
|
7
8
|
* A getter meant to be overridden in child classes. This does
|
@@ -76,11 +77,12 @@ class BaseBackgroundedModel extends dream_1.Dream {
|
|
76
77
|
* @param methodName - the name of the static method you wish to run in the background
|
77
78
|
* @param args - a variadic list of arguments to be sent to your method
|
78
79
|
*/
|
79
|
-
static async backgroundWithDelay(
|
80
|
+
static async backgroundWithDelay(delay, methodName, ...args) {
|
80
81
|
const safeThis = this;
|
81
82
|
return await index_js_1.default.staticMethod(safeThis, methodName, {
|
82
83
|
globalName: safeThis.globalName,
|
83
|
-
delaySeconds,
|
84
|
+
delaySeconds: (0, durationToSeconds_js_1.default)(delay),
|
85
|
+
jobId: delay.jobId,
|
84
86
|
args,
|
85
87
|
jobConfig: safeThis.backgroundJobConfig,
|
86
88
|
});
|
@@ -143,11 +145,12 @@ class BaseBackgroundedModel extends dream_1.Dream {
|
|
143
145
|
* @param methodName - the name of the static method you wish to run in the background
|
144
146
|
* @param args - a variadic list of arguments to be sent to your method
|
145
147
|
*/
|
146
|
-
async backgroundWithDelay(
|
148
|
+
async backgroundWithDelay(delay, methodName, ...args) {
|
147
149
|
const safeThis = this;
|
148
150
|
return await index_js_1.default.modelInstanceMethod(safeThis, methodName, {
|
149
151
|
args,
|
150
|
-
delaySeconds,
|
152
|
+
delaySeconds: (0, durationToSeconds_js_1.default)(delay),
|
153
|
+
jobId: delay.jobId,
|
151
154
|
jobConfig: safeThis.backgroundJobConfig,
|
152
155
|
});
|
153
156
|
}
|
@@ -2,6 +2,7 @@
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
const dream_1 = require("@rvoh/dream");
|
4
4
|
const index_js_1 = require("./index.js");
|
5
|
+
const durationToSeconds_js_1 = require("../helpers/durationToSeconds.js");
|
5
6
|
class BaseBackgroundedService {
|
6
7
|
/**
|
7
8
|
* A getter meant to be overridden in child classes. This does
|
@@ -88,11 +89,12 @@ class BaseBackgroundedService {
|
|
88
89
|
* @param methodName - the name of the static method you wish to run in the background
|
89
90
|
* @param args - a variadic list of arguments to be sent to your method
|
90
91
|
*/
|
91
|
-
static async backgroundWithDelay(
|
92
|
+
static async backgroundWithDelay(delay, methodName, ...args) {
|
92
93
|
const safeThis = this;
|
93
94
|
return await index_js_1.default.staticMethod(safeThis, methodName, {
|
94
95
|
globalName: safeThis.globalName,
|
95
|
-
delaySeconds,
|
96
|
+
delaySeconds: (0, durationToSeconds_js_1.default)(delay),
|
97
|
+
jobId: delay.jobId,
|
96
98
|
args,
|
97
99
|
jobConfig: safeThis.backgroundJobConfig,
|
98
100
|
});
|
@@ -14,6 +14,7 @@ const NoQueueForSpecifiedWorkstream_js_1 = require("../error/background/NoQueueF
|
|
14
14
|
const EnvInternal_js_1 = require("../helpers/EnvInternal.js");
|
15
15
|
const index_js_1 = require("../psychic-app-workers/index.js");
|
16
16
|
const nameToRedisQueueName_js_1 = require("./helpers/nameToRedisQueueName.js");
|
17
|
+
const DEFAULT_CONCURRENCY = 10;
|
17
18
|
/**
|
18
19
|
* the underlying class driving the `background` singleton,
|
19
20
|
* available as an import from `psychic-workers`.
|
@@ -130,13 +131,14 @@ class Background {
|
|
130
131
|
}
|
131
132
|
async shutdownAndExit() {
|
132
133
|
await this.shutdown();
|
133
|
-
|
134
|
+
// https://docs.bullmq.io/guide/going-to-production#gracefully-shut-down-workers
|
135
|
+
process.exit(0);
|
134
136
|
}
|
135
137
|
/**
|
136
138
|
* Shuts down workers, closes all redis connections
|
137
139
|
*/
|
138
140
|
async shutdown() {
|
139
|
-
|
141
|
+
index_js_1.default.getOrFail().psychicApp.logger.info(`[psychic-workers] shutdown`);
|
140
142
|
const psychicWorkersApp = index_js_1.default.getOrFail();
|
141
143
|
for (const hook of psychicWorkersApp.hooks.workerShutdown) {
|
142
144
|
await hook();
|
@@ -148,19 +150,12 @@ class Background {
|
|
148
150
|
* closes all redis connections for workers and queues
|
149
151
|
*/
|
150
152
|
async closeAllRedisConnections() {
|
151
|
-
|
152
|
-
await queue.close();
|
153
|
-
}
|
153
|
+
index_js_1.default.getOrFail().psychicApp.logger.info(`[psychic-workers] closeAllRedisConnections`);
|
154
154
|
for (const worker of this.workers) {
|
155
155
|
await worker.close();
|
156
156
|
}
|
157
157
|
for (const connection of this.redisConnections) {
|
158
|
-
|
159
|
-
connection.disconnect();
|
160
|
-
}
|
161
|
-
catch {
|
162
|
-
// noop
|
163
|
-
}
|
158
|
+
await connection.quit();
|
164
159
|
}
|
165
160
|
}
|
166
161
|
/**
|
@@ -205,7 +200,7 @@ class Background {
|
|
205
200
|
for (let i = 0; i < workerCount; i++) {
|
206
201
|
this._workers.push(new Background.Worker(formattedQueueName, async (job) => await this.doWork(job), {
|
207
202
|
connection: defaultWorkerConnection,
|
208
|
-
concurrency: backgroundOptions.defaultWorkstream?.concurrency ||
|
203
|
+
concurrency: backgroundOptions.defaultWorkstream?.concurrency || DEFAULT_CONCURRENCY,
|
209
204
|
}));
|
210
205
|
}
|
211
206
|
}
|
@@ -252,7 +247,7 @@ class Background {
|
|
252
247
|
limit: namedWorkstream.rateLimit,
|
253
248
|
},
|
254
249
|
connection: namedWorkstreamWorkerConnection,
|
255
|
-
concurrency: namedWorkstream.concurrency ||
|
250
|
+
concurrency: namedWorkstream.concurrency || DEFAULT_CONCURRENCY,
|
256
251
|
// explicitly typing as WorkerOptions because Psychic can't be aware of BullMQ Pro options
|
257
252
|
}));
|
258
253
|
}
|
@@ -322,6 +317,7 @@ class Background {
|
|
322
317
|
/////////////////////////
|
323
318
|
const namedQueueOptionsMap = nativeBullMQ.namedQueueOptions || {};
|
324
319
|
Object.keys(namedQueueOptionsMap).forEach(queueName => {
|
320
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
325
321
|
const namedQueueOptions = namedQueueOptionsMap[queueName];
|
326
322
|
if (namedQueueOptions.queueConnection)
|
327
323
|
this.redisConnections.push(namedQueueOptions.queueConnection);
|
@@ -344,7 +340,7 @@ class Background {
|
|
344
340
|
const extraWorkerOptions = extraWorkerOptionsMap[queueName];
|
345
341
|
const extraWorkerCount = extraWorkerOptions ? (extraWorkerOptions.workerCount ?? 1) : 0;
|
346
342
|
this.groupNames[queueName] ||= [];
|
347
|
-
if (extraWorkerOptions
|
343
|
+
if (extraWorkerOptions?.group?.id)
|
348
344
|
this.groupNames[queueName].push(extraWorkerOptions.group.id);
|
349
345
|
if (activateWorkers) {
|
350
346
|
if (!namedWorkerConnection)
|
@@ -370,11 +366,13 @@ class Background {
|
|
370
366
|
work() {
|
371
367
|
this.connect({ activateWorkers: true });
|
372
368
|
process.on('SIGTERM', () => {
|
369
|
+
index_js_1.default.getOrFail().psychicApp.logger.info('[psychic-workers] handle SIGTERM');
|
373
370
|
void this.shutdownAndExit()
|
374
371
|
.then(() => { })
|
375
372
|
.catch(() => { });
|
376
373
|
});
|
377
374
|
process.on('SIGINT', () => {
|
375
|
+
index_js_1.default.getOrFail().psychicApp.logger.info('[psychic-workers] handle SIGINT');
|
378
376
|
void this.shutdownAndExit()
|
379
377
|
.then(() => { })
|
380
378
|
.catch(() => { });
|
@@ -391,7 +389,7 @@ class Background {
|
|
391
389
|
* @param importKey - (optional) the import key for the class
|
392
390
|
* @param jobConfig - (optional) the background job config to use when backgrounding this method
|
393
391
|
*/
|
394
|
-
async staticMethod(ObjectClass, method, { globalName, delaySeconds, args = [], jobConfig = {}, }) {
|
392
|
+
async staticMethod(ObjectClass, method, { globalName, delaySeconds, jobId, args = [], jobConfig = {}, }) {
|
395
393
|
this.connect();
|
396
394
|
await this._addToQueue(`BackgroundJobQueueStaticJob`, {
|
397
395
|
globalName,
|
@@ -399,6 +397,7 @@ class Background {
|
|
399
397
|
args,
|
400
398
|
}, {
|
401
399
|
delaySeconds,
|
400
|
+
jobId,
|
402
401
|
jobConfig,
|
403
402
|
groupId: this.jobConfigToGroupId(jobConfig),
|
404
403
|
priority: this.jobConfigToPriority(jobConfig),
|
@@ -425,7 +424,10 @@ class Background {
|
|
425
424
|
//
|
426
425
|
// See: https://docs.bullmq.io/guide/jobs/repeatable
|
427
426
|
const jobId = `${ObjectClass.name}:${method}`;
|
428
|
-
|
427
|
+
const queueInstance = this.queueInstance(jobConfig);
|
428
|
+
if (!queueInstance)
|
429
|
+
throw new Error(`Missing queue for: ${jobConfig.queue?.toString()}`);
|
430
|
+
await queueInstance.add('BackgroundJobQueueStaticJob', {
|
429
431
|
globalName,
|
430
432
|
method,
|
431
433
|
args,
|
@@ -468,7 +470,7 @@ class Background {
|
|
468
470
|
* @param importKey - (optional) the import key for the class
|
469
471
|
* @param jobConfig - (optional) the background job config to use when backgrounding this method
|
470
472
|
*/
|
471
|
-
async modelInstanceMethod(modelInstance, method, { delaySeconds, args = [], jobConfig = {}, }) {
|
473
|
+
async modelInstanceMethod(modelInstance, method, { delaySeconds, jobId, args = [], jobConfig = {}, }) {
|
472
474
|
this.connect();
|
473
475
|
await this._addToQueue('BackgroundJobQueueModelInstanceJob', {
|
474
476
|
id: modelInstance.primaryKeyValue,
|
@@ -477,26 +479,33 @@ class Background {
|
|
477
479
|
args,
|
478
480
|
}, {
|
479
481
|
delaySeconds,
|
482
|
+
jobId,
|
480
483
|
jobConfig,
|
481
484
|
groupId: this.jobConfigToGroupId(jobConfig),
|
482
485
|
priority: this.jobConfigToPriority(jobConfig),
|
483
486
|
});
|
484
487
|
}
|
485
488
|
// should be private, but public so we can test
|
486
|
-
async _addToQueue(jobType, jobData, { delaySeconds, jobConfig, priority, groupId, }) {
|
489
|
+
async _addToQueue(jobType, jobData, { delaySeconds, jobId, jobConfig, priority, groupId, }) {
|
487
490
|
// set this variable out side of the conditional so that
|
488
491
|
// mismatches will raise exceptions even in tests
|
489
492
|
const queueInstance = this.queueInstance(jobConfig);
|
493
|
+
// if delaySeconds is 0, we will intentionally treat
|
494
|
+
// this as `undefined`
|
490
495
|
const delay = delaySeconds ? delaySeconds * 1000 : undefined;
|
491
496
|
if (EnvInternal_js_1.default.isTest && !EnvInternal_js_1.default.boolean('REALLY_TEST_BACKGROUND_QUEUE')) {
|
492
497
|
const queue = new Background.Queue('TestQueue', { connection: {} });
|
493
498
|
const job = new bullmq_1.Job(queue, jobType, jobData, {});
|
494
499
|
await this.doWork(job);
|
500
|
+
return;
|
495
501
|
//
|
496
502
|
}
|
497
|
-
|
503
|
+
if (!queueInstance)
|
504
|
+
throw new Error(`missing queue: ${jobConfig?.queue?.toString() || 'N/A'}`);
|
505
|
+
if (groupId && priority) {
|
498
506
|
await queueInstance.add(jobType, jobData, {
|
499
507
|
delay,
|
508
|
+
jobId,
|
500
509
|
group: {
|
501
510
|
...this.groupIdToGroupConfig(groupId),
|
502
511
|
priority: this.mapPriorityWordToPriorityNumber(priority),
|
@@ -508,6 +517,7 @@ class Background {
|
|
508
517
|
else {
|
509
518
|
await queueInstance.add(jobType, jobData, {
|
510
519
|
delay,
|
520
|
+
jobId,
|
511
521
|
group: this.groupIdToGroupConfig(groupId),
|
512
522
|
priority: this.mapPriorityWordToPriorityNumber(priority),
|
513
523
|
// explicitly typing as JobsOptions because Psychic can't be aware of BullMQ Pro options
|
@@ -0,0 +1,9 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.default = durationToSeconds;
|
4
|
+
function durationToSeconds(duration) {
|
5
|
+
return ((duration.seconds ? duration.seconds : 0) +
|
6
|
+
(duration.minutes ? duration.minutes * 60 : 0) +
|
7
|
+
(duration.hours ? duration.hours * 60 * 60 : 0) +
|
8
|
+
(duration.days ? duration.days * 60 * 60 * 24 : 0));
|
9
|
+
}
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import { Dream } from '@rvoh/dream';
|
2
2
|
import background from './index.js';
|
3
|
+
import durationToSeconds from '../helpers/durationToSeconds.js';
|
3
4
|
export default class BaseBackgroundedModel extends Dream {
|
4
5
|
/**
|
5
6
|
* A getter meant to be overridden in child classes. This does
|
@@ -74,11 +75,12 @@ export default class BaseBackgroundedModel extends Dream {
|
|
74
75
|
* @param methodName - the name of the static method you wish to run in the background
|
75
76
|
* @param args - a variadic list of arguments to be sent to your method
|
76
77
|
*/
|
77
|
-
static async backgroundWithDelay(
|
78
|
+
static async backgroundWithDelay(delay, methodName, ...args) {
|
78
79
|
const safeThis = this;
|
79
80
|
return await background.staticMethod(safeThis, methodName, {
|
80
81
|
globalName: safeThis.globalName,
|
81
|
-
delaySeconds,
|
82
|
+
delaySeconds: durationToSeconds(delay),
|
83
|
+
jobId: delay.jobId,
|
82
84
|
args,
|
83
85
|
jobConfig: safeThis.backgroundJobConfig,
|
84
86
|
});
|
@@ -141,11 +143,12 @@ export default class BaseBackgroundedModel extends Dream {
|
|
141
143
|
* @param methodName - the name of the static method you wish to run in the background
|
142
144
|
* @param args - a variadic list of arguments to be sent to your method
|
143
145
|
*/
|
144
|
-
async backgroundWithDelay(
|
146
|
+
async backgroundWithDelay(delay, methodName, ...args) {
|
145
147
|
const safeThis = this;
|
146
148
|
return await background.modelInstanceMethod(safeThis, methodName, {
|
147
149
|
args,
|
148
|
-
delaySeconds,
|
150
|
+
delaySeconds: durationToSeconds(delay),
|
151
|
+
jobId: delay.jobId,
|
149
152
|
jobConfig: safeThis.backgroundJobConfig,
|
150
153
|
});
|
151
154
|
}
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import { GlobalNameNotSet } from '@rvoh/dream';
|
2
2
|
import background from './index.js';
|
3
|
+
import durationToSeconds from '../helpers/durationToSeconds.js';
|
3
4
|
export default class BaseBackgroundedService {
|
4
5
|
/**
|
5
6
|
* A getter meant to be overridden in child classes. This does
|
@@ -86,11 +87,12 @@ export default class BaseBackgroundedService {
|
|
86
87
|
* @param methodName - the name of the static method you wish to run in the background
|
87
88
|
* @param args - a variadic list of arguments to be sent to your method
|
88
89
|
*/
|
89
|
-
static async backgroundWithDelay(
|
90
|
+
static async backgroundWithDelay(delay, methodName, ...args) {
|
90
91
|
const safeThis = this;
|
91
92
|
return await background.staticMethod(safeThis, methodName, {
|
92
93
|
globalName: safeThis.globalName,
|
93
|
-
delaySeconds,
|
94
|
+
delaySeconds: durationToSeconds(delay),
|
95
|
+
jobId: delay.jobId,
|
94
96
|
args,
|
95
97
|
jobConfig: safeThis.backgroundJobConfig,
|
96
98
|
});
|
@@ -10,6 +10,7 @@ import NoQueueForSpecifiedWorkstream from '../error/background/NoQueueForSpecifi
|
|
10
10
|
import EnvInternal from '../helpers/EnvInternal.js';
|
11
11
|
import PsychicAppWorkers from '../psychic-app-workers/index.js';
|
12
12
|
import nameToRedisQueueName from './helpers/nameToRedisQueueName.js';
|
13
|
+
const DEFAULT_CONCURRENCY = 10;
|
13
14
|
/**
|
14
15
|
* the underlying class driving the `background` singleton,
|
15
16
|
* available as an import from `psychic-workers`.
|
@@ -126,13 +127,14 @@ export class Background {
|
|
126
127
|
}
|
127
128
|
async shutdownAndExit() {
|
128
129
|
await this.shutdown();
|
129
|
-
|
130
|
+
// https://docs.bullmq.io/guide/going-to-production#gracefully-shut-down-workers
|
131
|
+
process.exit(0);
|
130
132
|
}
|
131
133
|
/**
|
132
134
|
* Shuts down workers, closes all redis connections
|
133
135
|
*/
|
134
136
|
async shutdown() {
|
135
|
-
|
137
|
+
PsychicAppWorkers.getOrFail().psychicApp.logger.info(`[psychic-workers] shutdown`);
|
136
138
|
const psychicWorkersApp = PsychicAppWorkers.getOrFail();
|
137
139
|
for (const hook of psychicWorkersApp.hooks.workerShutdown) {
|
138
140
|
await hook();
|
@@ -144,19 +146,12 @@ export class Background {
|
|
144
146
|
* closes all redis connections for workers and queues
|
145
147
|
*/
|
146
148
|
async closeAllRedisConnections() {
|
147
|
-
|
148
|
-
await queue.close();
|
149
|
-
}
|
149
|
+
PsychicAppWorkers.getOrFail().psychicApp.logger.info(`[psychic-workers] closeAllRedisConnections`);
|
150
150
|
for (const worker of this.workers) {
|
151
151
|
await worker.close();
|
152
152
|
}
|
153
153
|
for (const connection of this.redisConnections) {
|
154
|
-
|
155
|
-
connection.disconnect();
|
156
|
-
}
|
157
|
-
catch {
|
158
|
-
// noop
|
159
|
-
}
|
154
|
+
await connection.quit();
|
160
155
|
}
|
161
156
|
}
|
162
157
|
/**
|
@@ -201,7 +196,7 @@ export class Background {
|
|
201
196
|
for (let i = 0; i < workerCount; i++) {
|
202
197
|
this._workers.push(new Background.Worker(formattedQueueName, async (job) => await this.doWork(job), {
|
203
198
|
connection: defaultWorkerConnection,
|
204
|
-
concurrency: backgroundOptions.defaultWorkstream?.concurrency ||
|
199
|
+
concurrency: backgroundOptions.defaultWorkstream?.concurrency || DEFAULT_CONCURRENCY,
|
205
200
|
}));
|
206
201
|
}
|
207
202
|
}
|
@@ -248,7 +243,7 @@ export class Background {
|
|
248
243
|
limit: namedWorkstream.rateLimit,
|
249
244
|
},
|
250
245
|
connection: namedWorkstreamWorkerConnection,
|
251
|
-
concurrency: namedWorkstream.concurrency ||
|
246
|
+
concurrency: namedWorkstream.concurrency || DEFAULT_CONCURRENCY,
|
252
247
|
// explicitly typing as WorkerOptions because Psychic can't be aware of BullMQ Pro options
|
253
248
|
}));
|
254
249
|
}
|
@@ -318,6 +313,7 @@ export class Background {
|
|
318
313
|
/////////////////////////
|
319
314
|
const namedQueueOptionsMap = nativeBullMQ.namedQueueOptions || {};
|
320
315
|
Object.keys(namedQueueOptionsMap).forEach(queueName => {
|
316
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
321
317
|
const namedQueueOptions = namedQueueOptionsMap[queueName];
|
322
318
|
if (namedQueueOptions.queueConnection)
|
323
319
|
this.redisConnections.push(namedQueueOptions.queueConnection);
|
@@ -340,7 +336,7 @@ export class Background {
|
|
340
336
|
const extraWorkerOptions = extraWorkerOptionsMap[queueName];
|
341
337
|
const extraWorkerCount = extraWorkerOptions ? (extraWorkerOptions.workerCount ?? 1) : 0;
|
342
338
|
this.groupNames[queueName] ||= [];
|
343
|
-
if (extraWorkerOptions
|
339
|
+
if (extraWorkerOptions?.group?.id)
|
344
340
|
this.groupNames[queueName].push(extraWorkerOptions.group.id);
|
345
341
|
if (activateWorkers) {
|
346
342
|
if (!namedWorkerConnection)
|
@@ -366,11 +362,13 @@ export class Background {
|
|
366
362
|
work() {
|
367
363
|
this.connect({ activateWorkers: true });
|
368
364
|
process.on('SIGTERM', () => {
|
365
|
+
PsychicAppWorkers.getOrFail().psychicApp.logger.info('[psychic-workers] handle SIGTERM');
|
369
366
|
void this.shutdownAndExit()
|
370
367
|
.then(() => { })
|
371
368
|
.catch(() => { });
|
372
369
|
});
|
373
370
|
process.on('SIGINT', () => {
|
371
|
+
PsychicAppWorkers.getOrFail().psychicApp.logger.info('[psychic-workers] handle SIGINT');
|
374
372
|
void this.shutdownAndExit()
|
375
373
|
.then(() => { })
|
376
374
|
.catch(() => { });
|
@@ -387,7 +385,7 @@ export class Background {
|
|
387
385
|
* @param importKey - (optional) the import key for the class
|
388
386
|
* @param jobConfig - (optional) the background job config to use when backgrounding this method
|
389
387
|
*/
|
390
|
-
async staticMethod(ObjectClass, method, { globalName, delaySeconds, args = [], jobConfig = {}, }) {
|
388
|
+
async staticMethod(ObjectClass, method, { globalName, delaySeconds, jobId, args = [], jobConfig = {}, }) {
|
391
389
|
this.connect();
|
392
390
|
await this._addToQueue(`BackgroundJobQueueStaticJob`, {
|
393
391
|
globalName,
|
@@ -395,6 +393,7 @@ export class Background {
|
|
395
393
|
args,
|
396
394
|
}, {
|
397
395
|
delaySeconds,
|
396
|
+
jobId,
|
398
397
|
jobConfig,
|
399
398
|
groupId: this.jobConfigToGroupId(jobConfig),
|
400
399
|
priority: this.jobConfigToPriority(jobConfig),
|
@@ -421,7 +420,10 @@ export class Background {
|
|
421
420
|
//
|
422
421
|
// See: https://docs.bullmq.io/guide/jobs/repeatable
|
423
422
|
const jobId = `${ObjectClass.name}:${method}`;
|
424
|
-
|
423
|
+
const queueInstance = this.queueInstance(jobConfig);
|
424
|
+
if (!queueInstance)
|
425
|
+
throw new Error(`Missing queue for: ${jobConfig.queue?.toString()}`);
|
426
|
+
await queueInstance.add('BackgroundJobQueueStaticJob', {
|
425
427
|
globalName,
|
426
428
|
method,
|
427
429
|
args,
|
@@ -464,7 +466,7 @@ export class Background {
|
|
464
466
|
* @param importKey - (optional) the import key for the class
|
465
467
|
* @param jobConfig - (optional) the background job config to use when backgrounding this method
|
466
468
|
*/
|
467
|
-
async modelInstanceMethod(modelInstance, method, { delaySeconds, args = [], jobConfig = {}, }) {
|
469
|
+
async modelInstanceMethod(modelInstance, method, { delaySeconds, jobId, args = [], jobConfig = {}, }) {
|
468
470
|
this.connect();
|
469
471
|
await this._addToQueue('BackgroundJobQueueModelInstanceJob', {
|
470
472
|
id: modelInstance.primaryKeyValue,
|
@@ -473,26 +475,33 @@ export class Background {
|
|
473
475
|
args,
|
474
476
|
}, {
|
475
477
|
delaySeconds,
|
478
|
+
jobId,
|
476
479
|
jobConfig,
|
477
480
|
groupId: this.jobConfigToGroupId(jobConfig),
|
478
481
|
priority: this.jobConfigToPriority(jobConfig),
|
479
482
|
});
|
480
483
|
}
|
481
484
|
// should be private, but public so we can test
|
482
|
-
async _addToQueue(jobType, jobData, { delaySeconds, jobConfig, priority, groupId, }) {
|
485
|
+
async _addToQueue(jobType, jobData, { delaySeconds, jobId, jobConfig, priority, groupId, }) {
|
483
486
|
// set this variable out side of the conditional so that
|
484
487
|
// mismatches will raise exceptions even in tests
|
485
488
|
const queueInstance = this.queueInstance(jobConfig);
|
489
|
+
// if delaySeconds is 0, we will intentionally treat
|
490
|
+
// this as `undefined`
|
486
491
|
const delay = delaySeconds ? delaySeconds * 1000 : undefined;
|
487
492
|
if (EnvInternal.isTest && !EnvInternal.boolean('REALLY_TEST_BACKGROUND_QUEUE')) {
|
488
493
|
const queue = new Background.Queue('TestQueue', { connection: {} });
|
489
494
|
const job = new Job(queue, jobType, jobData, {});
|
490
495
|
await this.doWork(job);
|
496
|
+
return;
|
491
497
|
//
|
492
498
|
}
|
493
|
-
|
499
|
+
if (!queueInstance)
|
500
|
+
throw new Error(`missing queue: ${jobConfig?.queue?.toString() || 'N/A'}`);
|
501
|
+
if (groupId && priority) {
|
494
502
|
await queueInstance.add(jobType, jobData, {
|
495
503
|
delay,
|
504
|
+
jobId,
|
496
505
|
group: {
|
497
506
|
...this.groupIdToGroupConfig(groupId),
|
498
507
|
priority: this.mapPriorityWordToPriorityNumber(priority),
|
@@ -504,6 +513,7 @@ export class Background {
|
|
504
513
|
else {
|
505
514
|
await queueInstance.add(jobType, jobData, {
|
506
515
|
delay,
|
516
|
+
jobId,
|
507
517
|
group: this.groupIdToGroupConfig(groupId),
|
508
518
|
priority: this.mapPriorityWordToPriorityNumber(priority),
|
509
519
|
// explicitly typing as JobsOptions because Psychic can't be aware of BullMQ Pro options
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Dream } from '@rvoh/dream';
|
2
|
-
import { BackgroundJobConfig } from '../types/background.js';
|
2
|
+
import { BackgroundJobConfig, DelayedJobOpts } from '../types/background.js';
|
3
3
|
import { FunctionPropertyNames } from '../types/utils.js';
|
4
4
|
import { BackgroundableMethodArgs } from './BaseBackgroundedService.js';
|
5
5
|
export default class BaseBackgroundedModel extends Dream {
|
@@ -64,7 +64,7 @@ export default class BaseBackgroundedModel extends Dream {
|
|
64
64
|
* @param methodName - the name of the static method you wish to run in the background
|
65
65
|
* @param args - a variadic list of arguments to be sent to your method
|
66
66
|
*/
|
67
|
-
static backgroundWithDelay<T, MethodName extends PsychicBackgroundedModelStaticMethods<T & typeof BaseBackgroundedModel>, MethodFunc extends T[MethodName & keyof T], MethodArgs extends BackgroundableMethodArgs<MethodFunc>>(this: T,
|
67
|
+
static backgroundWithDelay<T, MethodName extends PsychicBackgroundedModelStaticMethods<T & typeof BaseBackgroundedModel>, MethodFunc extends T[MethodName & keyof T], MethodArgs extends BackgroundableMethodArgs<MethodFunc>>(this: T, delay: DelayedJobOpts, methodName: MethodName, ...args: MethodArgs): Promise<void>;
|
68
68
|
/**
|
69
69
|
* types composed by psychic must be provided, since psychic-workers leverages
|
70
70
|
* the sync command in psychic to read your backgroundable services and extract
|
@@ -114,7 +114,7 @@ export default class BaseBackgroundedModel extends Dream {
|
|
114
114
|
* @param methodName - the name of the static method you wish to run in the background
|
115
115
|
* @param args - a variadic list of arguments to be sent to your method
|
116
116
|
*/
|
117
|
-
backgroundWithDelay<T, MethodName extends PsychicBackgroundedServiceInstanceMethods<T & BaseBackgroundedModel>, MethodFunc extends T[MethodName & keyof T], MethodArgs extends BackgroundableMethodArgs<MethodFunc>>(this: T,
|
117
|
+
backgroundWithDelay<T, MethodName extends PsychicBackgroundedServiceInstanceMethods<T & BaseBackgroundedModel>, MethodFunc extends T[MethodName & keyof T], MethodArgs extends BackgroundableMethodArgs<MethodFunc>>(this: T, delay: DelayedJobOpts, methodName: MethodName, ...args: MethodArgs): Promise<void>;
|
118
118
|
}
|
119
119
|
export type PsychicBackgroundedModelStaticMethods<T extends typeof BaseBackgroundedModel> = Exclude<FunctionPropertyNames<Required<T>>, FunctionPropertyNames<typeof BaseBackgroundedModel>>;
|
120
120
|
export type PsychicBackgroundedServiceInstanceMethods<T extends BaseBackgroundedModel> = Exclude<FunctionPropertyNames<Required<T>>, FunctionPropertyNames<BaseBackgroundedModel>>;
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Job } from 'bullmq';
|
2
|
-
import { BackgroundJobConfig } from '../types/background.js';
|
2
|
+
import { BackgroundJobConfig, DelayedJobOpts } from '../types/background.js';
|
3
3
|
import { FunctionPropertyNames } from '../types/utils.js';
|
4
4
|
export default class BaseBackgroundedService {
|
5
5
|
/**
|
@@ -72,7 +72,7 @@ export default class BaseBackgroundedService {
|
|
72
72
|
* @param methodName - the name of the static method you wish to run in the background
|
73
73
|
* @param args - a variadic list of arguments to be sent to your method
|
74
74
|
*/
|
75
|
-
static backgroundWithDelay<T, MethodName extends PsychicBackgroundedServiceStaticMethods<T & typeof BaseBackgroundedService>, MethodFunc extends T[MethodName & keyof T], MethodArgs extends BackgroundableMethodArgs<MethodFunc>>(this: T,
|
75
|
+
static backgroundWithDelay<T, MethodName extends PsychicBackgroundedServiceStaticMethods<T & typeof BaseBackgroundedService>, MethodFunc extends T[MethodName & keyof T], MethodArgs extends BackgroundableMethodArgs<MethodFunc>>(this: T, delay: DelayedJobOpts, methodName: MethodName, ...args: MethodArgs): Promise<void>;
|
76
76
|
/**
|
77
77
|
* types composed by psychic must be provided, since psychic-workers leverages
|
78
78
|
* the sync command in psychic to read your backgroundable services and extract
|
@@ -124,11 +124,12 @@ export declare class Background {
|
|
124
124
|
* @param importKey - (optional) the import key for the class
|
125
125
|
* @param jobConfig - (optional) the background job config to use when backgrounding this method
|
126
126
|
*/
|
127
|
-
staticMethod(ObjectClass: Record<'name', string>, method: string, { globalName, delaySeconds, args, jobConfig, }: {
|
127
|
+
staticMethod(ObjectClass: Record<'name', string>, method: string, { globalName, delaySeconds, jobId, args, jobConfig, }: {
|
128
128
|
globalName: string;
|
129
129
|
args?: any[];
|
130
130
|
filepath?: string;
|
131
131
|
delaySeconds?: number;
|
132
|
+
jobId?: string | undefined;
|
132
133
|
importKey?: string;
|
133
134
|
jobConfig?: BackgroundJobConfig<any>;
|
134
135
|
}): Promise<void>;
|
@@ -162,17 +163,19 @@ export declare class Background {
|
|
162
163
|
* @param importKey - (optional) the import key for the class
|
163
164
|
* @param jobConfig - (optional) the background job config to use when backgrounding this method
|
164
165
|
*/
|
165
|
-
modelInstanceMethod(modelInstance: Dream, method: string, { delaySeconds, args, jobConfig, }: {
|
166
|
+
modelInstanceMethod(modelInstance: Dream, method: string, { delaySeconds, jobId, args, jobConfig, }: {
|
166
167
|
delaySeconds?: number;
|
168
|
+
jobId?: string | undefined;
|
167
169
|
importKey?: string;
|
168
170
|
args?: any[];
|
169
171
|
jobConfig?: BackgroundJobConfig<any>;
|
170
172
|
}): Promise<void>;
|
171
|
-
_addToQueue(jobType: JobTypes, jobData: BackgroundJobData, { delaySeconds, jobConfig, priority, groupId, }: {
|
172
|
-
delaySeconds?: number;
|
173
|
+
_addToQueue(jobType: JobTypes, jobData: BackgroundJobData, { delaySeconds, jobId, jobConfig, priority, groupId, }: {
|
174
|
+
delaySeconds?: number | undefined;
|
175
|
+
jobId?: string | undefined;
|
173
176
|
jobConfig: BackgroundJobConfig<any>;
|
174
177
|
priority: BackgroundQueuePriority;
|
175
|
-
groupId?: string;
|
178
|
+
groupId?: string | undefined;
|
176
179
|
}): Promise<void>;
|
177
180
|
private jobConfigToPriority;
|
178
181
|
private jobConfigToGroupId;
|
@@ -103,7 +103,7 @@ export type QueueOptionsWithConnectionInstance = Omit<QueueOptions, 'connection'
|
|
103
103
|
* queue and worker connections.
|
104
104
|
*/
|
105
105
|
queueConnection?: RedisOrRedisClusterConnection;
|
106
|
-
workerConnection?: RedisOrRedisClusterConnection;
|
106
|
+
workerConnection?: RedisOrRedisClusterConnection | undefined;
|
107
107
|
};
|
108
108
|
export interface PsychicBackgroundSimpleOptions extends PsychicBackgroundSharedOptions {
|
109
109
|
/**
|
@@ -114,7 +114,7 @@ export interface PsychicBackgroundSimpleOptions extends PsychicBackgroundSharedO
|
|
114
114
|
/**
|
115
115
|
* defaultWorkerConnection is only optional when workers will not be activated (e.g. on the webserver)
|
116
116
|
*/
|
117
|
-
defaultWorkerConnection
|
117
|
+
defaultWorkerConnection: RedisOrRedisClusterConnection | undefined;
|
118
118
|
/**
|
119
119
|
* Every Psychic application that leverages simple background jobs will have a default
|
120
120
|
* workstream. Set workerCount to set the number of workers that will work through the
|
@@ -30,6 +30,20 @@ export interface BackgroundJobData {
|
|
30
30
|
*/
|
31
31
|
globalName?: string;
|
32
32
|
}
|
33
|
+
export type DelayedJobOpts = DelayedJobDuration & {
|
34
|
+
/**
|
35
|
+
* a unique identifier for your job. this identifier will be
|
36
|
+
* used to debounce, leveraging the internal throttling mechanisms
|
37
|
+
* provided by BullMQ
|
38
|
+
*/
|
39
|
+
jobId?: string;
|
40
|
+
};
|
41
|
+
export interface DelayedJobDuration {
|
42
|
+
seconds?: number;
|
43
|
+
minutes?: number;
|
44
|
+
hours?: number;
|
45
|
+
days?: number;
|
46
|
+
}
|
33
47
|
export type JobTypes = 'BackgroundJobQueueFunctionJob' | 'BackgroundJobQueueStaticJob' | 'BackgroundJobQueueModelInstanceJob';
|
34
48
|
export type BackgroundQueuePriority = 'default' | 'urgent' | 'not_urgent' | 'last';
|
35
49
|
interface BaseBackgroundJobConfig {
|