@nest-batch/bullmq 0.2.0 → 0.2.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/README.md +7 -6
- package/dist/src/adapters/bullmq.adapter.d.ts +2 -2
- package/dist/src/adapters/bullmq.adapter.d.ts.map +1 -1
- package/dist/src/adapters/bullmq.adapter.js +9 -9
- package/dist/src/adapters/bullmq.adapter.js.map +1 -1
- package/dist/src/bullmq-execution-strategy.d.ts +3 -3
- package/dist/src/bullmq-execution-strategy.d.ts.map +1 -1
- package/dist/src/bullmq-execution-strategy.js +4 -4
- package/dist/src/bullmq-execution-strategy.js.map +1 -1
- package/dist/src/bullmq-runtime.d.ts +237 -0
- package/dist/src/bullmq-runtime.d.ts.map +1 -0
- package/dist/src/bullmq-runtime.js +441 -0
- package/dist/src/bullmq-runtime.js.map +1 -0
- package/dist/src/bullmq-schedule.d.ts +134 -0
- package/dist/src/bullmq-schedule.d.ts.map +1 -0
- package/dist/src/bullmq-schedule.js +290 -0
- package/dist/src/bullmq-schedule.js.map +1 -0
- package/dist/src/bullmq-schedule.service.d.ts +21 -9
- package/dist/src/bullmq-schedule.service.d.ts.map +1 -1
- package/dist/src/bullmq-schedule.service.js +57 -3
- package/dist/src/bullmq-schedule.service.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/package.json +3 -3
- package/src/adapters/bullmq.adapter.ts +17 -21
- package/src/bullmq-execution-strategy.ts +3 -6
- package/src/{bullmq-runtime.service.ts → bullmq-runtime.ts} +10 -17
- package/src/{bullmq-schedule.service.ts → bullmq-schedule.ts} +110 -22
- package/src/index.ts +1 -1
package/dist/src/index.js
CHANGED
|
@@ -27,7 +27,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
27
27
|
_export_star(require("./connection"), exports);
|
|
28
28
|
_export_star(require("./module-options"), exports);
|
|
29
29
|
_export_star(require("./bullmq-execution-strategy"), exports);
|
|
30
|
-
_export_star(require("./bullmq-schedule
|
|
30
|
+
_export_star(require("./bullmq-schedule"), exports);
|
|
31
31
|
_export_star(require("./adapters"), exports);
|
|
32
32
|
function _export_star(from, to) {
|
|
33
33
|
Object.keys(from).forEach(function(k) {
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/index.ts"],"sourcesContent":["/**\n * Public API barrel for `@nest-batch/bullmq`.\n *\n * The host application should depend exclusively on this barrel:\n * - `BullmqAdapter` is the new factory-pattern transport\n * adapter (use with `NestBatchModule.forRoot({ adapters:\n * { transport: BullmqAdapter.forRoot(...) } })`).\n * - `BullMqExecutionStrategy` is the strategy class (also\n * exported individually so callers can inject it directly for\n * inspection / health checks).\n * - `BULLMQ_MODULE_OPTIONS` is the DI token for the resolved\n * module options bag.\n * - the connection helpers are re-exported so callers can build\n * a fully-resolved `BullMqResolvedConnection` from a partial\n * `BullMqConnectionOptions` without importing the internal\n * `connection.ts` file.\n *\n * The legacy `BullmqBatchModule` (with `forRoot` / `forRootAsync`\n * static methods) has been replaced by `BullmqAdapter`. Internal\n * modules (`./bullmq-execution-strategy`, `./module-options`,\n * `./connection`, `./adapters/bullmq.module`) are implementation\n * details and may move between releases.\n */\nexport * from './connection';\nexport * from './module-options';\nexport * from './bullmq-execution-strategy';\nexport * from './bullmq-schedule
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts"],"sourcesContent":["/**\n * Public API barrel for `@nest-batch/bullmq`.\n *\n * The host application should depend exclusively on this barrel:\n * - `BullmqAdapter` is the new factory-pattern transport\n * adapter (use with `NestBatchModule.forRoot({ adapters:\n * { transport: BullmqAdapter.forRoot(...) } })`).\n * - `BullMqExecutionStrategy` is the strategy class (also\n * exported individually so callers can inject it directly for\n * inspection / health checks).\n * - `BULLMQ_MODULE_OPTIONS` is the DI token for the resolved\n * module options bag.\n * - the connection helpers are re-exported so callers can build\n * a fully-resolved `BullMqResolvedConnection` from a partial\n * `BullMqConnectionOptions` without importing the internal\n * `connection.ts` file.\n *\n * The legacy `BullmqBatchModule` (with `forRoot` / `forRootAsync`\n * static methods) has been replaced by `BullmqAdapter`. Internal\n * modules (`./bullmq-execution-strategy`, `./module-options`,\n * `./connection`, `./adapters/bullmq.module`) are implementation\n * details and may move between releases.\n */\nexport * from './connection';\nexport * from './module-options';\nexport * from './bullmq-execution-strategy';\nexport * from './bullmq-schedule';\nexport * from './adapters';\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;CAsBC;;;;qBACa;qBACA;qBACA;qBACA;qBACA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nest-batch/bullmq",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "BullMQ runtime adapter for @nest-batch/core — runs batch jobs and partitioned steps as BullMQ queues and workers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "easdkr",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@nestjs/common": "^10 || ^11",
|
|
35
35
|
"@nestjs/core": "^10 || ^11",
|
|
36
36
|
"bullmq": "^5.0.0",
|
|
37
|
-
"@nest-batch/core": "^0.2.
|
|
37
|
+
"@nest-batch/core": "^0.2.1"
|
|
38
38
|
},
|
|
39
39
|
"peerDependenciesMeta": {
|
|
40
40
|
"@nest-batch/core": {
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"reflect-metadata": "^0.2.2",
|
|
61
61
|
"typescript": "^5.5.0",
|
|
62
62
|
"vitest": "^2.0.0",
|
|
63
|
-
"@nest-batch/core": "0.2.
|
|
63
|
+
"@nest-batch/core": "0.2.1"
|
|
64
64
|
},
|
|
65
65
|
"scripts": {
|
|
66
66
|
"build": "swc src -d dist --config-file ../../.swcrc && tsc --emitDeclarationOnly -p tsconfig.build.json",
|
|
@@ -2,8 +2,8 @@ import { Module, type DynamicModule, type Provider } from '@nestjs/common';
|
|
|
2
2
|
import { EXECUTION_STRATEGY, type BatchAdapter } from '@nest-batch/core';
|
|
3
3
|
|
|
4
4
|
import { BullMqExecutionStrategy } from '../bullmq-execution-strategy';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { BullmqRuntime } from '../bullmq-runtime';
|
|
6
|
+
import { BullmqSchedule } from '../bullmq-schedule';
|
|
7
7
|
import { resolveBullMqConnection } from '../connection';
|
|
8
8
|
import {
|
|
9
9
|
BULLMQ_MODULE_OPTIONS,
|
|
@@ -53,7 +53,7 @@ const OPTIONS_FACTORY: symbol = Symbol.for('@nest-batch/bullmq/OPTIONS_FACTORY')
|
|
|
53
53
|
*
|
|
54
54
|
* The set mirrors the legacy `BullmqBatchModule.forRoot()` exports
|
|
55
55
|
* (the `forRootAsync()` legacy path was missing
|
|
56
|
-
* `
|
|
56
|
+
* `BullmqRuntime` from `exports` — that omission is fixed
|
|
57
57
|
* here, both paths now export the same five entries).
|
|
58
58
|
*
|
|
59
59
|
* - `EXECUTION_STRATEGY` — the DI token, so host code (e.g. a
|
|
@@ -64,17 +64,19 @@ const OPTIONS_FACTORY: symbol = Symbol.for('@nest-batch/bullmq/OPTIONS_FACTORY')
|
|
|
64
64
|
* builders.
|
|
65
65
|
* - `BullMqExecutionStrategy` — the concrete class, for type-
|
|
66
66
|
* strict consumers that prefer class injection.
|
|
67
|
-
* - `
|
|
67
|
+
* - `BullmqRuntime` — the runtime that owns the
|
|
68
68
|
* `Queue` / `Worker` / `QueueEvents` lifecycle.
|
|
69
|
-
* - `
|
|
69
|
+
* - `BullmqSchedule` — the runtime that owns the
|
|
70
70
|
* `@BatchScheduled` cron-to-BullMQ translation.
|
|
71
71
|
*/
|
|
72
|
-
const ADAPTER_EXPORTS: ReadonlyArray<
|
|
72
|
+
const ADAPTER_EXPORTS: ReadonlyArray<
|
|
73
|
+
symbol | typeof BullMqExecutionStrategy | typeof BullmqRuntime | typeof BullmqSchedule
|
|
74
|
+
> = [
|
|
73
75
|
EXECUTION_STRATEGY,
|
|
74
76
|
BULLMQ_MODULE_OPTIONS,
|
|
75
77
|
BullMqExecutionStrategy,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
BullmqRuntime,
|
|
79
|
+
BullmqSchedule,
|
|
78
80
|
];
|
|
79
81
|
|
|
80
82
|
/**
|
|
@@ -85,8 +87,8 @@ const ADAPTER_EXPORTS: ReadonlyArray<symbol | typeof BullMqExecutionStrategy | t
|
|
|
85
87
|
* Overrides the default `EXECUTION_STRATEGY` token with a BullMQ-
|
|
86
88
|
* backed `IExecutionStrategy` (`BullMqExecutionStrategy`) and wires
|
|
87
89
|
* the runtime services that own the BullMQ client lifecycle
|
|
88
|
-
* (`
|
|
89
|
-
* `
|
|
90
|
+
* (`BullmqRuntime` for step enqueue + worker, plus
|
|
91
|
+
* `BullmqSchedule` for `@BatchScheduled` cron entries).
|
|
90
92
|
*
|
|
91
93
|
* Two static methods:
|
|
92
94
|
*
|
|
@@ -226,9 +228,7 @@ export class BullmqAdapter {
|
|
|
226
228
|
static forRootAsync(asyncOptions: {
|
|
227
229
|
imports?: DynamicModule['imports'];
|
|
228
230
|
inject?: readonly unknown[];
|
|
229
|
-
useFactory: (
|
|
230
|
-
...args: unknown[]
|
|
231
|
-
) => Promise<BullMqModuleOptions> | BullMqModuleOptions;
|
|
231
|
+
useFactory: (...args: unknown[]) => Promise<BullMqModuleOptions> | BullMqModuleOptions;
|
|
232
232
|
}): BatchAdapter {
|
|
233
233
|
const factoryProvider: Provider = {
|
|
234
234
|
provide: OPTIONS_FACTORY,
|
|
@@ -238,9 +238,7 @@ export class BullmqAdapter {
|
|
|
238
238
|
|
|
239
239
|
const mergedOptionsProvider: Provider = {
|
|
240
240
|
provide: BULLMQ_MODULE_OPTIONS,
|
|
241
|
-
useFactory: (
|
|
242
|
-
fromFactory: BullMqModuleOptions | undefined,
|
|
243
|
-
): ResolvedBullMqModuleOptions => {
|
|
241
|
+
useFactory: (fromFactory: BullMqModuleOptions | undefined): ResolvedBullMqModuleOptions => {
|
|
244
242
|
return Object.freeze({
|
|
245
243
|
connection: resolveBullMqConnection(fromFactory?.connection),
|
|
246
244
|
autoStartWorker: fromFactory?.autoStartWorker ?? false,
|
|
@@ -295,13 +293,11 @@ export class BullmqAdapter {
|
|
|
295
293
|
* out of the returned array and replaces it with the factory
|
|
296
294
|
* pair. Everything else is shared.
|
|
297
295
|
*/
|
|
298
|
-
function buildStaticProviders(
|
|
299
|
-
resolved: ResolvedBullMqModuleOptions,
|
|
300
|
-
): Provider[] {
|
|
296
|
+
function buildStaticProviders(resolved: ResolvedBullMqModuleOptions): Provider[] {
|
|
301
297
|
return [
|
|
302
298
|
BullMqExecutionStrategy,
|
|
303
|
-
|
|
304
|
-
|
|
299
|
+
BullmqRuntime,
|
|
300
|
+
BullmqSchedule,
|
|
305
301
|
{
|
|
306
302
|
provide: EXECUTION_STRATEGY,
|
|
307
303
|
useExisting: BullMqExecutionStrategy,
|
|
@@ -8,10 +8,7 @@ import {
|
|
|
8
8
|
type LaunchResult,
|
|
9
9
|
} from '@nest-batch/core';
|
|
10
10
|
|
|
11
|
-
import {
|
|
12
|
-
BullmqRuntimeService,
|
|
13
|
-
BULLMQ_STRATEGY_NAME,
|
|
14
|
-
} from './bullmq-runtime.service';
|
|
11
|
+
import { BullmqRuntime, BULLMQ_STRATEGY_NAME } from './bullmq-runtime';
|
|
15
12
|
|
|
16
13
|
/**
|
|
17
14
|
* BullMQ execution strategy — the `@nest-batch/core`-facing
|
|
@@ -21,7 +18,7 @@ import {
|
|
|
21
18
|
* Design (T18):
|
|
22
19
|
* - The actual BullMQ resource ownership (queue / worker /
|
|
23
20
|
* queue-events lifecycle, connection tuning, event bridge)
|
|
24
|
-
* lives in `
|
|
21
|
+
* lives in `BullmqRuntime`. This class is a thin
|
|
25
22
|
* adapter that maps the `IExecutionStrategy` contract to
|
|
26
23
|
* the runtime service's `launch()` shape.
|
|
27
24
|
* - Splitting the two lets the runtime service be
|
|
@@ -51,7 +48,7 @@ export class BullMqExecutionStrategy implements IExecutionStrategy {
|
|
|
51
48
|
|
|
52
49
|
private readonly logger = new Logger(BullMqExecutionStrategy.name);
|
|
53
50
|
|
|
54
|
-
constructor(private readonly runtime:
|
|
51
|
+
constructor(private readonly runtime: BullmqRuntime) {}
|
|
55
52
|
|
|
56
53
|
/**
|
|
57
54
|
* Enqueue the work and return the BullMQ job id. The DB
|
|
@@ -20,10 +20,7 @@ import {
|
|
|
20
20
|
} from '@nest-batch/core';
|
|
21
21
|
import { JobExecutor, JobRegistry, NoopBatchObserver, BATCH_EVENT } from '@nest-batch/core';
|
|
22
22
|
|
|
23
|
-
import {
|
|
24
|
-
BULLMQ_MODULE_OPTIONS,
|
|
25
|
-
type ResolvedBullMqModuleOptions,
|
|
26
|
-
} from './module-options';
|
|
23
|
+
import { BULLMQ_MODULE_OPTIONS, type ResolvedBullMqModuleOptions } from './module-options';
|
|
27
24
|
|
|
28
25
|
/**
|
|
29
26
|
* Payload shape stored in a BullMQ job's `data` field.
|
|
@@ -118,7 +115,7 @@ export const BULLMQ_STRATEGY_NAME = 'bullmq';
|
|
|
118
115
|
* of truth and a single method (`close()`).
|
|
119
116
|
*/
|
|
120
117
|
@Injectable()
|
|
121
|
-
export class
|
|
118
|
+
export class BullmqRuntime
|
|
122
119
|
implements IExecutionStrategy, OnApplicationBootstrap, OnApplicationShutdown
|
|
123
120
|
{
|
|
124
121
|
/**
|
|
@@ -127,7 +124,7 @@ export class BullmqRuntimeService
|
|
|
127
124
|
*/
|
|
128
125
|
readonly name = BULLMQ_STRATEGY_NAME;
|
|
129
126
|
|
|
130
|
-
private readonly logger = new Logger(
|
|
127
|
+
private readonly logger = new Logger(BullmqRuntime.name);
|
|
131
128
|
|
|
132
129
|
/** BullMQ queue (producer side). */
|
|
133
130
|
private queue: Queue | null = null;
|
|
@@ -178,12 +175,12 @@ export class BullmqRuntimeService
|
|
|
178
175
|
if (this.options.autoStartWorker) {
|
|
179
176
|
this.worker = this.buildWorker();
|
|
180
177
|
this.logger.log(
|
|
181
|
-
`
|
|
178
|
+
`BullmqRuntime started: queue="${BULLMQ_QUEUE_NAME}" ` +
|
|
182
179
|
`worker=auto, keyPrefix="${this.options.connection.keyPrefix}"`,
|
|
183
180
|
);
|
|
184
181
|
} else {
|
|
185
182
|
this.logger.log(
|
|
186
|
-
`
|
|
183
|
+
`BullmqRuntime started: queue="${BULLMQ_QUEUE_NAME}" ` +
|
|
187
184
|
`worker=manual (autoStartWorker=false)`,
|
|
188
185
|
);
|
|
189
186
|
}
|
|
@@ -241,7 +238,7 @@ export class BullmqRuntimeService
|
|
|
241
238
|
): Promise<{ kind: 'enqueued'; queueJobId: string }> {
|
|
242
239
|
if (this.queue === null) {
|
|
243
240
|
throw new Error(
|
|
244
|
-
`[
|
|
241
|
+
`[BullmqRuntime] launch() called before onApplicationBootstrap — ` +
|
|
245
242
|
'module is not initialized. Did you forget to import BullmqBatchModule?',
|
|
246
243
|
);
|
|
247
244
|
}
|
|
@@ -284,9 +281,7 @@ export class BullmqRuntimeService
|
|
|
284
281
|
// (which is disabled by `enableOfflineQueue: false`) is
|
|
285
282
|
// not available. Surface this as a hard error so the
|
|
286
283
|
// launcher propagates the failure.
|
|
287
|
-
throw new Error(
|
|
288
|
-
`[BullmqRuntimeService] enqueue returned undefined job id (Redis down?)`,
|
|
289
|
-
);
|
|
284
|
+
throw new Error(`[BullmqRuntime] enqueue returned undefined job id (Redis down?)`);
|
|
290
285
|
}
|
|
291
286
|
const qid = String(enqueued.id);
|
|
292
287
|
lastQueueJobId = qid;
|
|
@@ -301,7 +296,7 @@ export class BullmqRuntimeService
|
|
|
301
296
|
// (partitionOrdinals has length >= 1), so this branch is
|
|
302
297
|
// unreachable in practice. Keep the explicit throw so a
|
|
303
298
|
// future refactor cannot quietly enqueue zero jobs.
|
|
304
|
-
throw new Error(`[
|
|
299
|
+
throw new Error(`[BullmqRuntime] enqueued zero jobs for execution ${ctx.executionId}`);
|
|
305
300
|
}
|
|
306
301
|
return { kind: 'enqueued', queueJobId: lastQueueJobId };
|
|
307
302
|
}
|
|
@@ -423,7 +418,7 @@ export class BullmqRuntimeService
|
|
|
423
418
|
// Surface as a BullMQ-level failure so the technical
|
|
424
419
|
// retry / dead-letter path handles it.
|
|
425
420
|
throw new Error(
|
|
426
|
-
`[
|
|
421
|
+
`[BullmqRuntime] JobExecution ${payload.executionId} not found in repository`,
|
|
427
422
|
);
|
|
428
423
|
}
|
|
429
424
|
const jobDef = this.registry.get(payload.jobId);
|
|
@@ -530,9 +525,7 @@ export class BullmqRuntimeService
|
|
|
530
525
|
try {
|
|
531
526
|
await this.queue.close();
|
|
532
527
|
} catch (err) {
|
|
533
|
-
this.logger.warn(
|
|
534
|
-
`Queue close failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
535
|
-
);
|
|
528
|
+
this.logger.warn(`Queue close failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
536
529
|
}
|
|
537
530
|
this.queue = null;
|
|
538
531
|
}
|
|
@@ -5,9 +5,14 @@ import {
|
|
|
5
5
|
OnApplicationBootstrap,
|
|
6
6
|
OnApplicationShutdown,
|
|
7
7
|
} from '@nestjs/common';
|
|
8
|
-
import { Queue, type JobsOptions } from 'bullmq';
|
|
8
|
+
import { Queue, Worker, type Job, type JobsOptions } from 'bullmq';
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
BatchScheduleRegistry,
|
|
12
|
+
JobLauncher,
|
|
13
|
+
type BatchScheduleEntry,
|
|
14
|
+
type JobParameters,
|
|
15
|
+
} from '@nest-batch/core';
|
|
11
16
|
|
|
12
17
|
import { BULLMQ_MODULE_OPTIONS, type ResolvedBullMqModuleOptions } from './module-options';
|
|
13
18
|
|
|
@@ -26,8 +31,14 @@ import { BULLMQ_MODULE_OPTIONS, type ResolvedBullMqModuleOptions } from './modul
|
|
|
26
31
|
*/
|
|
27
32
|
export const BULLMQ_SCHEDULE_QUEUE_NAME = 'nest-batch-schedule';
|
|
28
33
|
|
|
34
|
+
export interface BullmqSchedulePayload {
|
|
35
|
+
readonly jobId: string;
|
|
36
|
+
readonly scheduleName: string;
|
|
37
|
+
readonly methodName: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
29
40
|
/**
|
|
30
|
-
* `
|
|
41
|
+
* `BullmqSchedule` — the runtime scheduler for
|
|
31
42
|
* `@BatchScheduled` entries.
|
|
32
43
|
*
|
|
33
44
|
* Lifecycle:
|
|
@@ -39,35 +50,39 @@ export const BULLMQ_SCHEDULE_QUEUE_NAME = 'nest-batch-schedule';
|
|
|
39
50
|
* 2. BullMQ's `upsertJobScheduler` internally fires the
|
|
40
51
|
* schedule at the configured cron time. Each fire enqueues a
|
|
41
52
|
* job into the schedule queue (named after the schedule
|
|
42
|
-
* entry's method).
|
|
43
|
-
*
|
|
44
|
-
*
|
|
53
|
+
* entry's method). When `autoStartWorker` is `true`, this
|
|
54
|
+
* service also starts a schedule-queue worker that bridges
|
|
55
|
+
* `{ jobId, scheduleName, methodName }` into
|
|
56
|
+
* `JobLauncher.launch(jobId, params)`.
|
|
45
57
|
* 3. `OnApplicationShutdown` removes every installed scheduler
|
|
46
58
|
* (via `queue.removeJobScheduler`) and closes the queue.
|
|
47
59
|
* Removal is best-effort: a partial failure logs a warning
|
|
48
60
|
* but does not block the rest of the shutdown.
|
|
49
61
|
*
|
|
50
|
-
* Why a dedicated service (not a method on `
|
|
62
|
+
* Why a dedicated service (not a method on `BullmqRuntime`)?
|
|
51
63
|
* - The runtime service is `IExecutionStrategy`-facing; it
|
|
52
64
|
* knows about `JobExecution`, the in-process launch contract,
|
|
53
65
|
* and the worker bridge. Mixing scheduler concerns in would
|
|
54
66
|
* bloat its surface and couple two lifecycles that happen to
|
|
55
67
|
* share a Redis client but are otherwise independent.
|
|
56
|
-
* - The
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
68
|
+
* - The schedule queue needs a different worker contract from
|
|
69
|
+
* the runtime work queue: schedule payloads identify a job to
|
|
70
|
+
* launch, while work payloads identify an already-created
|
|
71
|
+
* execution/step. Keeping the bridge here avoids teaching
|
|
72
|
+
* `BullmqRuntime` a second payload shape.
|
|
60
73
|
* - The schedule service owns its own `Queue` (the schedule
|
|
61
74
|
* queue) so cron jobs are not interleaved with manually-launched
|
|
62
75
|
* jobs. They share the same `keyPrefix` so the host's Redis
|
|
63
76
|
* namespace policy still applies.
|
|
64
77
|
*/
|
|
65
78
|
@Injectable()
|
|
66
|
-
export class
|
|
67
|
-
private readonly logger = new Logger(
|
|
79
|
+
export class BullmqSchedule implements OnApplicationBootstrap, OnApplicationShutdown {
|
|
80
|
+
private readonly logger = new Logger(BullmqSchedule.name);
|
|
68
81
|
|
|
69
82
|
/** BullMQ queue for the scheduler (producer side only). */
|
|
70
83
|
private scheduleQueue: Queue | null = null;
|
|
84
|
+
/** BullMQ worker that turns schedule fires into real batch launches. */
|
|
85
|
+
private scheduleWorker: Worker<BullmqSchedulePayload> | null = null;
|
|
71
86
|
|
|
72
87
|
/**
|
|
73
88
|
* Every schedule key installed during `onApplicationBootstrap`.
|
|
@@ -84,6 +99,7 @@ export class BullmqScheduleService implements OnApplicationBootstrap, OnApplicat
|
|
|
84
99
|
private readonly scheduleRegistry: BatchScheduleRegistry,
|
|
85
100
|
@Inject(BULLMQ_MODULE_OPTIONS)
|
|
86
101
|
private readonly options: ResolvedBullMqModuleOptions,
|
|
102
|
+
private readonly launcher: JobLauncher,
|
|
87
103
|
) {}
|
|
88
104
|
|
|
89
105
|
/**
|
|
@@ -101,21 +117,25 @@ export class BullmqScheduleService implements OnApplicationBootstrap, OnApplicat
|
|
|
101
117
|
*/
|
|
102
118
|
onApplicationBootstrap(): void {
|
|
103
119
|
this.scheduleQueue = this.buildScheduleQueue();
|
|
120
|
+
if (this.options.autoStartWorker) {
|
|
121
|
+
this.scheduleWorker = this.buildScheduleWorker();
|
|
122
|
+
}
|
|
104
123
|
const entries = this.scheduleRegistry.getAll();
|
|
105
124
|
for (const entry of entries) {
|
|
106
125
|
try {
|
|
107
126
|
this.installSchedule(entry);
|
|
108
127
|
} catch (err) {
|
|
109
128
|
this.logger.warn(
|
|
110
|
-
`Failed to install schedule for "${entry.jobId}::${entry.
|
|
129
|
+
`Failed to install schedule for "${entry.jobId}::${entry.scheduleName}": ` +
|
|
111
130
|
`${err instanceof Error ? err.message : String(err)}`,
|
|
112
131
|
);
|
|
113
132
|
}
|
|
114
133
|
}
|
|
115
134
|
this.logger.log(
|
|
116
|
-
`
|
|
135
|
+
`BullmqSchedule started: queue="${BULLMQ_SCHEDULE_QUEUE_NAME}" ` +
|
|
117
136
|
`schedules=${this.installedKeys.size}/${entries.length} ` +
|
|
118
|
-
`(skipped=${entries.length - this.installedKeys.size} inert)
|
|
137
|
+
`(skipped=${entries.length - this.installedKeys.size} inert) ` +
|
|
138
|
+
`worker=${this.options.autoStartWorker ? 'auto' : 'manual'}`,
|
|
119
139
|
);
|
|
120
140
|
}
|
|
121
141
|
|
|
@@ -154,7 +174,7 @@ export class BullmqScheduleService implements OnApplicationBootstrap, OnApplicat
|
|
|
154
174
|
private installSchedule(entry: BatchScheduleEntry): void {
|
|
155
175
|
if (entry.inert) {
|
|
156
176
|
this.logger.log(
|
|
157
|
-
`Skipping inert schedule: ${entry.jobId}::${entry.
|
|
177
|
+
`Skipping inert schedule: ${entry.jobId}::${entry.scheduleName} ` +
|
|
158
178
|
`(cron="${entry.cron}", tz="${entry.timezone}")`,
|
|
159
179
|
);
|
|
160
180
|
return;
|
|
@@ -164,16 +184,20 @@ export class BullmqScheduleService implements OnApplicationBootstrap, OnApplicat
|
|
|
164
184
|
// builds the queue before iterating entries, but a future
|
|
165
185
|
// refactor that calls `installSchedule` from elsewhere
|
|
166
186
|
// should fail loudly.
|
|
167
|
-
throw new Error('[
|
|
187
|
+
throw new Error('[BullmqSchedule] scheduleQueue is null');
|
|
168
188
|
}
|
|
169
|
-
const schedulerKey = `${entry.jobId}::${entry.
|
|
189
|
+
const schedulerKey = `${entry.jobId}::${entry.scheduleName}`;
|
|
170
190
|
const template: {
|
|
171
191
|
name: string;
|
|
172
|
-
data:
|
|
192
|
+
data: BullmqSchedulePayload;
|
|
173
193
|
opts: JobsOptions;
|
|
174
194
|
} = {
|
|
175
|
-
name: entry.
|
|
176
|
-
data: {
|
|
195
|
+
name: entry.scheduleName,
|
|
196
|
+
data: {
|
|
197
|
+
jobId: entry.jobId,
|
|
198
|
+
scheduleName: entry.scheduleName,
|
|
199
|
+
methodName: entry.methodName,
|
|
200
|
+
},
|
|
177
201
|
opts: {
|
|
178
202
|
attempts: 3,
|
|
179
203
|
backoff: { type: 'exponential', delay: 100, jitter: 0.5 },
|
|
@@ -221,6 +245,47 @@ export class BullmqScheduleService implements OnApplicationBootstrap, OnApplicat
|
|
|
221
245
|
});
|
|
222
246
|
}
|
|
223
247
|
|
|
248
|
+
private buildScheduleWorker(): Worker<BullmqSchedulePayload> {
|
|
249
|
+
return new Worker<BullmqSchedulePayload>(
|
|
250
|
+
BULLMQ_SCHEDULE_QUEUE_NAME,
|
|
251
|
+
async (job) => this.processScheduleFire(job),
|
|
252
|
+
{
|
|
253
|
+
connection: this.workerConnectionOptions(),
|
|
254
|
+
prefix: this.options.connection.keyPrefix,
|
|
255
|
+
concurrency: 1,
|
|
256
|
+
},
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private async processScheduleFire(job: Job<BullmqSchedulePayload>): Promise<void> {
|
|
261
|
+
const { jobId, scheduleName, methodName } = job.data;
|
|
262
|
+
if (typeof jobId !== 'string' || jobId.length === 0) {
|
|
263
|
+
throw new Error('[BullmqSchedule] schedule payload is missing jobId');
|
|
264
|
+
}
|
|
265
|
+
if (typeof scheduleName !== 'string' || scheduleName.length === 0) {
|
|
266
|
+
throw new Error('[BullmqSchedule] schedule payload is missing scheduleName');
|
|
267
|
+
}
|
|
268
|
+
if (typeof methodName !== 'string' || methodName.length === 0) {
|
|
269
|
+
throw new Error('[BullmqSchedule] schedule payload is missing methodName');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const scheduledAt =
|
|
273
|
+
typeof job.timestamp === 'number' && Number.isFinite(job.timestamp)
|
|
274
|
+
? new Date(job.timestamp).toISOString()
|
|
275
|
+
: new Date().toISOString();
|
|
276
|
+
const params: JobParameters = {
|
|
277
|
+
scheduled: true,
|
|
278
|
+
scheduleName,
|
|
279
|
+
scheduledAt,
|
|
280
|
+
scheduleQueueJobId: String(job.id ?? ''),
|
|
281
|
+
};
|
|
282
|
+
const execution = await this.launcher.launch(jobId, params);
|
|
283
|
+
this.logger.log(
|
|
284
|
+
`Fired schedule ${jobId}::${scheduleName} ` +
|
|
285
|
+
`(method=${methodName}) -> execution=${execution.id} status=${execution.status}`,
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
224
289
|
private producerConnectionOptions(): Record<string, unknown> {
|
|
225
290
|
return {
|
|
226
291
|
host: this.options.connection.host,
|
|
@@ -234,6 +299,19 @@ export class BullmqScheduleService implements OnApplicationBootstrap, OnApplicat
|
|
|
234
299
|
};
|
|
235
300
|
}
|
|
236
301
|
|
|
302
|
+
private workerConnectionOptions(): Record<string, unknown> {
|
|
303
|
+
return {
|
|
304
|
+
host: this.options.connection.host,
|
|
305
|
+
port: this.options.connection.port,
|
|
306
|
+
password: this.options.connection.password,
|
|
307
|
+
username: this.options.connection.username,
|
|
308
|
+
db: this.options.connection.db,
|
|
309
|
+
...(this.options.connection.tls ? { tls: true } : {}),
|
|
310
|
+
maxRetriesPerRequest: null,
|
|
311
|
+
enableReadyCheck: false,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
237
315
|
// -------------------------------------------------------------------------
|
|
238
316
|
// Close
|
|
239
317
|
// -------------------------------------------------------------------------
|
|
@@ -246,6 +324,16 @@ export class BullmqScheduleService implements OnApplicationBootstrap, OnApplicat
|
|
|
246
324
|
* others from being removed.
|
|
247
325
|
*/
|
|
248
326
|
private async close(): Promise<void> {
|
|
327
|
+
if (this.scheduleWorker !== null) {
|
|
328
|
+
try {
|
|
329
|
+
await this.scheduleWorker.close();
|
|
330
|
+
} catch (err) {
|
|
331
|
+
this.logger.warn(
|
|
332
|
+
`Schedule worker close failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
this.scheduleWorker = null;
|
|
336
|
+
}
|
|
249
337
|
if (this.scheduleQueue !== null) {
|
|
250
338
|
for (const key of this.installedKeys) {
|
|
251
339
|
try {
|
package/src/index.ts
CHANGED