@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/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.service"), exports);
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) {
@@ -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.service';\nexport * from './adapters';\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;CAsBC;;;;qBACa;qBACA;qBACA;qBACA;qBACA"}
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.0",
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.0"
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.0"
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 { BullmqRuntimeService } from '../bullmq-runtime.service';
6
- import { BullmqScheduleService } from '../bullmq-schedule.service';
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
- * `BullmqRuntimeService` from `exports` — that omission is fixed
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
- * - `BullmqRuntimeService` — the runtime that owns the
67
+ * - `BullmqRuntime` — the runtime that owns the
68
68
  * `Queue` / `Worker` / `QueueEvents` lifecycle.
69
- * - `BullmqScheduleService` — the runtime that owns the
69
+ * - `BullmqSchedule` — the runtime that owns the
70
70
  * `@BatchScheduled` cron-to-BullMQ translation.
71
71
  */
72
- const ADAPTER_EXPORTS: ReadonlyArray<symbol | typeof BullMqExecutionStrategy | typeof BullmqRuntimeService | typeof BullmqScheduleService> = [
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
- BullmqRuntimeService,
77
- BullmqScheduleService,
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
- * (`BullmqRuntimeService` for step enqueue + worker, plus
89
- * `BullmqScheduleService` for `@BatchScheduled` cron entries).
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
- BullmqRuntimeService,
304
- BullmqScheduleService,
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 `BullmqRuntimeService`. This class is a thin
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: BullmqRuntimeService) {}
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 BullmqRuntimeService
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(BullmqRuntimeService.name);
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
- `BullmqRuntimeService started: queue="${BULLMQ_QUEUE_NAME}" ` +
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
- `BullmqRuntimeService started: queue="${BULLMQ_QUEUE_NAME}" ` +
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
- `[BullmqRuntimeService] launch() called before onApplicationBootstrap — ` +
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(`[BullmqRuntimeService] enqueued zero jobs for execution ${ctx.executionId}`);
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
- `[BullmqRuntimeService] JobExecution ${payload.executionId} not found in repository`,
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 { BATCH_SCHEDULE_REGISTRY, BatchScheduleRegistry, type BatchScheduleEntry } from '@nest-batch/core';
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
- * `BullmqScheduleService` — the runtime scheduler for
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). A separate `Worker` (the one owned by
43
- * `BullmqRuntimeService` if `autoStartWorker` is `true`)
44
- * processes the jobs.
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 `BullmqRuntimeService`)?
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 scheduler does NOT need a `Worker`; the runtime service
57
- * does. A separate service can run with `autoStartWorker:
58
- * false` cleanly (a launcher-only deployment that still wants
59
- * cron schedules to fire).
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 BullmqScheduleService implements OnApplicationBootstrap, OnApplicationShutdown {
67
- private readonly logger = new Logger(BullmqScheduleService.name);
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.methodName}": ` +
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
- `BullmqScheduleService started: queue="${BULLMQ_SCHEDULE_QUEUE_NAME}" ` +
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.methodName} ` +
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('[BullmqScheduleService] scheduleQueue is null');
187
+ throw new Error('[BullmqSchedule] scheduleQueue is null');
168
188
  }
169
- const schedulerKey = `${entry.jobId}::${entry.methodName}`;
189
+ const schedulerKey = `${entry.jobId}::${entry.scheduleName}`;
170
190
  const template: {
171
191
  name: string;
172
- data: Record<string, unknown>;
192
+ data: BullmqSchedulePayload;
173
193
  opts: JobsOptions;
174
194
  } = {
175
- name: entry.methodName,
176
- data: { jobId: entry.jobId, methodName: entry.methodName },
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
@@ -24,5 +24,5 @@
24
24
  export * from './connection';
25
25
  export * from './module-options';
26
26
  export * from './bullmq-execution-strategy';
27
- export * from './bullmq-schedule.service';
27
+ export * from './bullmq-schedule';
28
28
  export * from './adapters';