@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.
@@ -0,0 +1,290 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: Object.getOwnPropertyDescriptor(all, name).get
9
+ });
10
+ }
11
+ _export(exports, {
12
+ get BULLMQ_SCHEDULE_QUEUE_NAME () {
13
+ return BULLMQ_SCHEDULE_QUEUE_NAME;
14
+ },
15
+ get BullmqSchedule () {
16
+ return BullmqSchedule;
17
+ }
18
+ });
19
+ const _common = require("@nestjs/common");
20
+ const _bullmq = require("bullmq");
21
+ const _core = require("@nest-batch/core");
22
+ const _moduleoptions = require("./module-options");
23
+ function _ts_decorate(decorators, target, key, desc) {
24
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
25
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
26
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
27
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
28
+ }
29
+ function _ts_metadata(k, v) {
30
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
31
+ }
32
+ function _ts_param(paramIndex, decorator) {
33
+ return function(target, key) {
34
+ decorator(target, key, paramIndex);
35
+ };
36
+ }
37
+ const BULLMQ_SCHEDULE_QUEUE_NAME = 'nest-batch-schedule';
38
+ let BullmqSchedule = class BullmqSchedule {
39
+ scheduleRegistry;
40
+ options;
41
+ launcher;
42
+ logger = new _common.Logger(BullmqSchedule.name);
43
+ /** BullMQ queue for the scheduler (producer side only). */ scheduleQueue = null;
44
+ /** BullMQ worker that turns schedule fires into real batch launches. */ scheduleWorker = null;
45
+ /**
46
+ * Every schedule key installed during `onApplicationBootstrap`.
47
+ * Tracked so the shutdown path can `removeJobScheduler` for
48
+ * each one deterministically. A `Set` keeps the test assertions
49
+ * order-independent.
50
+ */ installedKeys = new Set();
51
+ /** Promise-chain lock for the close path. Mirrors the runtime service. */ closePromise = null;
52
+ constructor(scheduleRegistry, options, launcher){
53
+ this.scheduleRegistry = scheduleRegistry;
54
+ this.options = options;
55
+ this.launcher = launcher;
56
+ }
57
+ /**
58
+ * Walk the registry and install every non-inert entry as a
59
+ * BullMQ repeating job. Runs AFTER the `BatchBootstrapper` has
60
+ * populated the registry (both hooks are on
61
+ * `OnApplicationBootstrap`, but Nest calls them in
62
+ * provider-registration order; the bootstrapper is registered
63
+ * before this service by `BullmqBatchModule.forRoot()`).
64
+ *
65
+ * Each entry is wrapped in a per-entry `try` so a single bad
66
+ * schedule does not abort the rest of the installation. Bad
67
+ * schedules are logged and skipped — the runtime keeps running
68
+ * for the valid ones.
69
+ */ onApplicationBootstrap() {
70
+ this.scheduleQueue = this.buildScheduleQueue();
71
+ if (this.options.autoStartWorker) {
72
+ this.scheduleWorker = this.buildScheduleWorker();
73
+ }
74
+ const entries = this.scheduleRegistry.getAll();
75
+ for (const entry of entries){
76
+ try {
77
+ this.installSchedule(entry);
78
+ } catch (err) {
79
+ this.logger.warn(`Failed to install schedule for "${entry.jobId}::${entry.scheduleName}": ` + `${err instanceof Error ? err.message : String(err)}`);
80
+ }
81
+ }
82
+ this.logger.log(`BullmqSchedule started: queue="${BULLMQ_SCHEDULE_QUEUE_NAME}" ` + `schedules=${this.installedKeys.size}/${entries.length} ` + `(skipped=${entries.length - this.installedKeys.size} inert) ` + `worker=${this.options.autoStartWorker ? 'auto' : 'manual'}`);
83
+ }
84
+ /**
85
+ * Tear down every installed scheduler and close the schedule
86
+ * queue. Idempotent: a second `onApplicationShutdown` short-
87
+ * circuits to the first close's promise.
88
+ */ async onApplicationShutdown() {
89
+ if (this.closePromise !== null) {
90
+ return this.closePromise;
91
+ }
92
+ this.closePromise = this.close();
93
+ return this.closePromise;
94
+ }
95
+ /**
96
+ * Installed scheduler keys, in insertion order. Exposed for
97
+ * tests and diagnostics. Read-only: callers MUST NOT mutate
98
+ * the returned array.
99
+ */ installedSchedulerKeys() {
100
+ return Array.from(this.installedKeys);
101
+ }
102
+ // -------------------------------------------------------------------------
103
+ // Installation
104
+ // -------------------------------------------------------------------------
105
+ /**
106
+ * Install a single entry as a BullMQ repeating job. Skips
107
+ * inert entries (the runtime honours the inert flag by NOT
108
+ * calling `upsertJobScheduler` for them). Throws on
109
+ * installation failure so the caller can log + continue.
110
+ */ installSchedule(entry) {
111
+ if (entry.inert) {
112
+ this.logger.log(`Skipping inert schedule: ${entry.jobId}::${entry.scheduleName} ` + `(cron="${entry.cron}", tz="${entry.timezone}")`);
113
+ return;
114
+ }
115
+ if (this.scheduleQueue === null) {
116
+ // Defensive: should never happen because `onApplicationBootstrap`
117
+ // builds the queue before iterating entries, but a future
118
+ // refactor that calls `installSchedule` from elsewhere
119
+ // should fail loudly.
120
+ throw new Error('[BullmqSchedule] scheduleQueue is null');
121
+ }
122
+ const schedulerKey = `${entry.jobId}::${entry.scheduleName}`;
123
+ const template = {
124
+ name: entry.scheduleName,
125
+ data: {
126
+ jobId: entry.jobId,
127
+ scheduleName: entry.scheduleName,
128
+ methodName: entry.methodName
129
+ },
130
+ opts: {
131
+ attempts: 3,
132
+ backoff: {
133
+ type: 'exponential',
134
+ delay: 100,
135
+ jitter: 0.5
136
+ },
137
+ removeOnComplete: {
138
+ count: 100,
139
+ age: 3600
140
+ },
141
+ removeOnFail: {
142
+ count: 1000
143
+ }
144
+ }
145
+ };
146
+ void this.scheduleQueue.upsertJobScheduler(schedulerKey, {
147
+ pattern: entry.cron,
148
+ tz: entry.timezone
149
+ }, template);
150
+ this.installedKeys.add(schedulerKey);
151
+ this.logger.log(`Installed schedule: ${schedulerKey} (cron="${entry.cron}", tz="${entry.timezone}")`);
152
+ }
153
+ // -------------------------------------------------------------------------
154
+ // Queue construction
155
+ // -------------------------------------------------------------------------
156
+ /**
157
+ * Build the producer-side BullMQ queue for the scheduler. The
158
+ * connection tuning mirrors the runtime service's producer
159
+ * options: fail-fast on Redis-down (`enableOfflineQueue:
160
+ * false`) and a tight per-request retry budget
161
+ * (`maxRetriesPerRequest: 1`).
162
+ */ buildScheduleQueue() {
163
+ return new _bullmq.Queue(BULLMQ_SCHEDULE_QUEUE_NAME, {
164
+ connection: this.producerConnectionOptions(),
165
+ defaultJobOptions: {
166
+ attempts: 3,
167
+ backoff: {
168
+ type: 'exponential',
169
+ delay: 100,
170
+ jitter: 0.5
171
+ },
172
+ removeOnComplete: {
173
+ count: 100,
174
+ age: 3600
175
+ },
176
+ removeOnFail: {
177
+ count: 1000
178
+ }
179
+ },
180
+ prefix: this.options.connection.keyPrefix,
181
+ skipWaitingForReady: true,
182
+ // Mirrors the runtime service: skip the constructor-time
183
+ // version probe so the queue does not throw on a Redis
184
+ // client that is not yet ready.
185
+ skipVersionCheck: true
186
+ });
187
+ }
188
+ buildScheduleWorker() {
189
+ return new _bullmq.Worker(BULLMQ_SCHEDULE_QUEUE_NAME, async (job)=>this.processScheduleFire(job), {
190
+ connection: this.workerConnectionOptions(),
191
+ prefix: this.options.connection.keyPrefix,
192
+ concurrency: 1
193
+ });
194
+ }
195
+ async processScheduleFire(job) {
196
+ const { jobId, scheduleName, methodName } = job.data;
197
+ if (typeof jobId !== 'string' || jobId.length === 0) {
198
+ throw new Error('[BullmqSchedule] schedule payload is missing jobId');
199
+ }
200
+ if (typeof scheduleName !== 'string' || scheduleName.length === 0) {
201
+ throw new Error('[BullmqSchedule] schedule payload is missing scheduleName');
202
+ }
203
+ if (typeof methodName !== 'string' || methodName.length === 0) {
204
+ throw new Error('[BullmqSchedule] schedule payload is missing methodName');
205
+ }
206
+ const scheduledAt = typeof job.timestamp === 'number' && Number.isFinite(job.timestamp) ? new Date(job.timestamp).toISOString() : new Date().toISOString();
207
+ const params = {
208
+ scheduled: true,
209
+ scheduleName,
210
+ scheduledAt,
211
+ scheduleQueueJobId: String(job.id ?? '')
212
+ };
213
+ const execution = await this.launcher.launch(jobId, params);
214
+ this.logger.log(`Fired schedule ${jobId}::${scheduleName} ` + `(method=${methodName}) -> execution=${execution.id} status=${execution.status}`);
215
+ }
216
+ producerConnectionOptions() {
217
+ return {
218
+ host: this.options.connection.host,
219
+ port: this.options.connection.port,
220
+ password: this.options.connection.password,
221
+ username: this.options.connection.username,
222
+ db: this.options.connection.db,
223
+ ...this.options.connection.tls ? {
224
+ tls: true
225
+ } : {},
226
+ enableOfflineQueue: false,
227
+ maxRetriesPerRequest: 1
228
+ };
229
+ }
230
+ workerConnectionOptions() {
231
+ return {
232
+ host: this.options.connection.host,
233
+ port: this.options.connection.port,
234
+ password: this.options.connection.password,
235
+ username: this.options.connection.username,
236
+ db: this.options.connection.db,
237
+ ...this.options.connection.tls ? {
238
+ tls: true
239
+ } : {},
240
+ maxRetriesPerRequest: null,
241
+ enableReadyCheck: false
242
+ };
243
+ }
244
+ // -------------------------------------------------------------------------
245
+ // Close
246
+ // -------------------------------------------------------------------------
247
+ /**
248
+ * Close the schedule queue. `removeJobScheduler` is called
249
+ * first for every installed key so the next run of the host
250
+ * app does not inherit leftover schedulers. Each removal is
251
+ * best-effort: a failure on one key does not prevent the
252
+ * others from being removed.
253
+ */ async close() {
254
+ if (this.scheduleWorker !== null) {
255
+ try {
256
+ await this.scheduleWorker.close();
257
+ } catch (err) {
258
+ this.logger.warn(`Schedule worker close failed: ${err instanceof Error ? err.message : String(err)}`);
259
+ }
260
+ this.scheduleWorker = null;
261
+ }
262
+ if (this.scheduleQueue !== null) {
263
+ for (const key of this.installedKeys){
264
+ try {
265
+ await this.scheduleQueue.removeJobScheduler(key);
266
+ } catch (err) {
267
+ this.logger.warn(`removeJobScheduler("${key}") failed: ${err instanceof Error ? err.message : String(err)}`);
268
+ }
269
+ }
270
+ try {
271
+ await this.scheduleQueue.close();
272
+ } catch (err) {
273
+ this.logger.warn(`Schedule queue close failed: ${err instanceof Error ? err.message : String(err)}`);
274
+ }
275
+ this.scheduleQueue = null;
276
+ }
277
+ }
278
+ };
279
+ BullmqSchedule = _ts_decorate([
280
+ (0, _common.Injectable)(),
281
+ _ts_param(1, (0, _common.Inject)(_moduleoptions.BULLMQ_MODULE_OPTIONS)),
282
+ _ts_metadata("design:type", Function),
283
+ _ts_metadata("design:paramtypes", [
284
+ typeof _core.BatchScheduleRegistry === "undefined" ? Object : _core.BatchScheduleRegistry,
285
+ typeof ResolvedBullMqModuleOptions === "undefined" ? Object : ResolvedBullMqModuleOptions,
286
+ typeof _core.JobLauncher === "undefined" ? Object : _core.JobLauncher
287
+ ])
288
+ ], BullmqSchedule);
289
+
290
+ //# sourceMappingURL=bullmq-schedule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/bullmq-schedule.ts"],"sourcesContent":["import {\n Inject,\n Injectable,\n Logger,\n OnApplicationBootstrap,\n OnApplicationShutdown,\n} from '@nestjs/common';\nimport { Queue, Worker, type Job, type JobsOptions } from 'bullmq';\n\nimport {\n BatchScheduleRegistry,\n JobLauncher,\n type BatchScheduleEntry,\n type JobParameters,\n} from '@nest-batch/core';\n\nimport { BULLMQ_MODULE_OPTIONS, type ResolvedBullMqModuleOptions } from './module-options';\n\n/**\n * The single BullMQ queue name used by the schedule service. We\n * intentionally use a DIFFERENT queue from the runtime service's\n * `BULLMQ_QUEUE_NAME` so cron-triggered jobs and ad-hoc\n * `launch()`-triggered jobs are inspectable in isolation (and so\n * the schedule-removal path on shutdown can tear them down\n * without touching the runtime work queue).\n *\n * BullMQ 5 rejects queue names that contain a colon (`:`) because\n * it is the path separator in the key layout. We use a hyphen\n * (`-`) instead, matching the existing `BULLMQ_QUEUE_NAME`\n * convention (`'nest-batch-work'`).\n */\nexport const BULLMQ_SCHEDULE_QUEUE_NAME = 'nest-batch-schedule';\n\nexport interface BullmqSchedulePayload {\n readonly jobId: string;\n readonly scheduleName: string;\n readonly methodName: string;\n}\n\n/**\n * `BullmqSchedule` — the runtime scheduler for\n * `@BatchScheduled` entries.\n *\n * Lifecycle:\n * 1. `OnApplicationBootstrap` walks the `BatchScheduleRegistry`\n * and, for every entry with `inert: false`, registers a\n * BullMQ repeating job via `queue.upsertJobScheduler(...)`.\n * Entries with `inert: true` are logged and skipped — that\n * is the only place the inert flag is consulted.\n * 2. BullMQ's `upsertJobScheduler` internally fires the\n * schedule at the configured cron time. Each fire enqueues a\n * job into the schedule queue (named after the schedule\n * entry's method). When `autoStartWorker` is `true`, this\n * service also starts a schedule-queue worker that bridges\n * `{ jobId, scheduleName, methodName }` into\n * `JobLauncher.launch(jobId, params)`.\n * 3. `OnApplicationShutdown` removes every installed scheduler\n * (via `queue.removeJobScheduler`) and closes the queue.\n * Removal is best-effort: a partial failure logs a warning\n * but does not block the rest of the shutdown.\n *\n * Why a dedicated service (not a method on `BullmqRuntime`)?\n * - The runtime service is `IExecutionStrategy`-facing; it\n * knows about `JobExecution`, the in-process launch contract,\n * and the worker bridge. Mixing scheduler concerns in would\n * bloat its surface and couple two lifecycles that happen to\n * share a Redis client but are otherwise independent.\n * - The schedule queue needs a different worker contract from\n * the runtime work queue: schedule payloads identify a job to\n * launch, while work payloads identify an already-created\n * execution/step. Keeping the bridge here avoids teaching\n * `BullmqRuntime` a second payload shape.\n * - The schedule service owns its own `Queue` (the schedule\n * queue) so cron jobs are not interleaved with manually-launched\n * jobs. They share the same `keyPrefix` so the host's Redis\n * namespace policy still applies.\n */\n@Injectable()\nexport class BullmqSchedule implements OnApplicationBootstrap, OnApplicationShutdown {\n private readonly logger = new Logger(BullmqSchedule.name);\n\n /** BullMQ queue for the scheduler (producer side only). */\n private scheduleQueue: Queue | null = null;\n /** BullMQ worker that turns schedule fires into real batch launches. */\n private scheduleWorker: Worker<BullmqSchedulePayload> | null = null;\n\n /**\n * Every schedule key installed during `onApplicationBootstrap`.\n * Tracked so the shutdown path can `removeJobScheduler` for\n * each one deterministically. A `Set` keeps the test assertions\n * order-independent.\n */\n private readonly installedKeys = new Set<string>();\n\n /** Promise-chain lock for the close path. Mirrors the runtime service. */\n private closePromise: Promise<void> | null = null;\n\n constructor(\n private readonly scheduleRegistry: BatchScheduleRegistry,\n @Inject(BULLMQ_MODULE_OPTIONS)\n private readonly options: ResolvedBullMqModuleOptions,\n private readonly launcher: JobLauncher,\n ) {}\n\n /**\n * Walk the registry and install every non-inert entry as a\n * BullMQ repeating job. Runs AFTER the `BatchBootstrapper` has\n * populated the registry (both hooks are on\n * `OnApplicationBootstrap`, but Nest calls them in\n * provider-registration order; the bootstrapper is registered\n * before this service by `BullmqBatchModule.forRoot()`).\n *\n * Each entry is wrapped in a per-entry `try` so a single bad\n * schedule does not abort the rest of the installation. Bad\n * schedules are logged and skipped — the runtime keeps running\n * for the valid ones.\n */\n onApplicationBootstrap(): void {\n this.scheduleQueue = this.buildScheduleQueue();\n if (this.options.autoStartWorker) {\n this.scheduleWorker = this.buildScheduleWorker();\n }\n const entries = this.scheduleRegistry.getAll();\n for (const entry of entries) {\n try {\n this.installSchedule(entry);\n } catch (err) {\n this.logger.warn(\n `Failed to install schedule for \"${entry.jobId}::${entry.scheduleName}\": ` +\n `${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n this.logger.log(\n `BullmqSchedule started: queue=\"${BULLMQ_SCHEDULE_QUEUE_NAME}\" ` +\n `schedules=${this.installedKeys.size}/${entries.length} ` +\n `(skipped=${entries.length - this.installedKeys.size} inert) ` +\n `worker=${this.options.autoStartWorker ? 'auto' : 'manual'}`,\n );\n }\n\n /**\n * Tear down every installed scheduler and close the schedule\n * queue. Idempotent: a second `onApplicationShutdown` short-\n * circuits to the first close's promise.\n */\n async onApplicationShutdown(): Promise<void> {\n if (this.closePromise !== null) {\n return this.closePromise;\n }\n this.closePromise = this.close();\n return this.closePromise;\n }\n\n /**\n * Installed scheduler keys, in insertion order. Exposed for\n * tests and diagnostics. Read-only: callers MUST NOT mutate\n * the returned array.\n */\n installedSchedulerKeys(): readonly string[] {\n return Array.from(this.installedKeys);\n }\n\n // -------------------------------------------------------------------------\n // Installation\n // -------------------------------------------------------------------------\n\n /**\n * Install a single entry as a BullMQ repeating job. Skips\n * inert entries (the runtime honours the inert flag by NOT\n * calling `upsertJobScheduler` for them). Throws on\n * installation failure so the caller can log + continue.\n */\n private installSchedule(entry: BatchScheduleEntry): void {\n if (entry.inert) {\n this.logger.log(\n `Skipping inert schedule: ${entry.jobId}::${entry.scheduleName} ` +\n `(cron=\"${entry.cron}\", tz=\"${entry.timezone}\")`,\n );\n return;\n }\n if (this.scheduleQueue === null) {\n // Defensive: should never happen because `onApplicationBootstrap`\n // builds the queue before iterating entries, but a future\n // refactor that calls `installSchedule` from elsewhere\n // should fail loudly.\n throw new Error('[BullmqSchedule] scheduleQueue is null');\n }\n const schedulerKey = `${entry.jobId}::${entry.scheduleName}`;\n const template: {\n name: string;\n data: BullmqSchedulePayload;\n opts: JobsOptions;\n } = {\n name: entry.scheduleName,\n data: {\n jobId: entry.jobId,\n scheduleName: entry.scheduleName,\n methodName: entry.methodName,\n },\n opts: {\n attempts: 3,\n backoff: { type: 'exponential', delay: 100, jitter: 0.5 },\n removeOnComplete: { count: 100, age: 3600 },\n removeOnFail: { count: 1000 },\n },\n };\n void this.scheduleQueue.upsertJobScheduler(\n schedulerKey,\n { pattern: entry.cron, tz: entry.timezone },\n template,\n );\n this.installedKeys.add(schedulerKey);\n this.logger.log(\n `Installed schedule: ${schedulerKey} (cron=\"${entry.cron}\", tz=\"${entry.timezone}\")`,\n );\n }\n\n // -------------------------------------------------------------------------\n // Queue construction\n // -------------------------------------------------------------------------\n\n /**\n * Build the producer-side BullMQ queue for the scheduler. The\n * connection tuning mirrors the runtime service's producer\n * options: fail-fast on Redis-down (`enableOfflineQueue:\n * false`) and a tight per-request retry budget\n * (`maxRetriesPerRequest: 1`).\n */\n private buildScheduleQueue(): Queue {\n return new Queue(BULLMQ_SCHEDULE_QUEUE_NAME, {\n connection: this.producerConnectionOptions(),\n defaultJobOptions: {\n attempts: 3,\n backoff: { type: 'exponential', delay: 100, jitter: 0.5 },\n removeOnComplete: { count: 100, age: 3600 },\n removeOnFail: { count: 1000 },\n },\n prefix: this.options.connection.keyPrefix,\n skipWaitingForReady: true,\n // Mirrors the runtime service: skip the constructor-time\n // version probe so the queue does not throw on a Redis\n // client that is not yet ready.\n skipVersionCheck: true,\n });\n }\n\n private buildScheduleWorker(): Worker<BullmqSchedulePayload> {\n return new Worker<BullmqSchedulePayload>(\n BULLMQ_SCHEDULE_QUEUE_NAME,\n async (job) => this.processScheduleFire(job),\n {\n connection: this.workerConnectionOptions(),\n prefix: this.options.connection.keyPrefix,\n concurrency: 1,\n },\n );\n }\n\n private async processScheduleFire(job: Job<BullmqSchedulePayload>): Promise<void> {\n const { jobId, scheduleName, methodName } = job.data;\n if (typeof jobId !== 'string' || jobId.length === 0) {\n throw new Error('[BullmqSchedule] schedule payload is missing jobId');\n }\n if (typeof scheduleName !== 'string' || scheduleName.length === 0) {\n throw new Error('[BullmqSchedule] schedule payload is missing scheduleName');\n }\n if (typeof methodName !== 'string' || methodName.length === 0) {\n throw new Error('[BullmqSchedule] schedule payload is missing methodName');\n }\n\n const scheduledAt =\n typeof job.timestamp === 'number' && Number.isFinite(job.timestamp)\n ? new Date(job.timestamp).toISOString()\n : new Date().toISOString();\n const params: JobParameters = {\n scheduled: true,\n scheduleName,\n scheduledAt,\n scheduleQueueJobId: String(job.id ?? ''),\n };\n const execution = await this.launcher.launch(jobId, params);\n this.logger.log(\n `Fired schedule ${jobId}::${scheduleName} ` +\n `(method=${methodName}) -> execution=${execution.id} status=${execution.status}`,\n );\n }\n\n private producerConnectionOptions(): Record<string, unknown> {\n return {\n host: this.options.connection.host,\n port: this.options.connection.port,\n password: this.options.connection.password,\n username: this.options.connection.username,\n db: this.options.connection.db,\n ...(this.options.connection.tls ? { tls: true } : {}),\n enableOfflineQueue: false,\n maxRetriesPerRequest: 1,\n };\n }\n\n private workerConnectionOptions(): Record<string, unknown> {\n return {\n host: this.options.connection.host,\n port: this.options.connection.port,\n password: this.options.connection.password,\n username: this.options.connection.username,\n db: this.options.connection.db,\n ...(this.options.connection.tls ? { tls: true } : {}),\n maxRetriesPerRequest: null,\n enableReadyCheck: false,\n };\n }\n\n // -------------------------------------------------------------------------\n // Close\n // -------------------------------------------------------------------------\n\n /**\n * Close the schedule queue. `removeJobScheduler` is called\n * first for every installed key so the next run of the host\n * app does not inherit leftover schedulers. Each removal is\n * best-effort: a failure on one key does not prevent the\n * others from being removed.\n */\n private async close(): Promise<void> {\n if (this.scheduleWorker !== null) {\n try {\n await this.scheduleWorker.close();\n } catch (err) {\n this.logger.warn(\n `Schedule worker close failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n this.scheduleWorker = null;\n }\n if (this.scheduleQueue !== null) {\n for (const key of this.installedKeys) {\n try {\n await this.scheduleQueue.removeJobScheduler(key);\n } catch (err) {\n this.logger.warn(\n `removeJobScheduler(\"${key}\") failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n try {\n await this.scheduleQueue.close();\n } catch (err) {\n this.logger.warn(\n `Schedule queue close failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n this.scheduleQueue = null;\n }\n }\n}\n"],"names":["BULLMQ_SCHEDULE_QUEUE_NAME","BullmqSchedule","logger","Logger","name","scheduleQueue","scheduleWorker","installedKeys","Set","closePromise","scheduleRegistry","options","launcher","onApplicationBootstrap","buildScheduleQueue","autoStartWorker","buildScheduleWorker","entries","getAll","entry","installSchedule","err","warn","jobId","scheduleName","Error","message","String","log","size","length","onApplicationShutdown","close","installedSchedulerKeys","Array","from","inert","cron","timezone","schedulerKey","template","data","methodName","opts","attempts","backoff","type","delay","jitter","removeOnComplete","count","age","removeOnFail","upsertJobScheduler","pattern","tz","add","Queue","connection","producerConnectionOptions","defaultJobOptions","prefix","keyPrefix","skipWaitingForReady","skipVersionCheck","Worker","job","processScheduleFire","workerConnectionOptions","concurrency","scheduledAt","timestamp","Number","isFinite","Date","toISOString","params","scheduled","scheduleQueueJobId","id","execution","launch","status","host","port","password","username","db","tls","enableOfflineQueue","maxRetriesPerRequest","enableReadyCheck","key","removeJobScheduler"],"mappings":";;;;;;;;;;;QA+BaA;eAAAA;;QA+CAC;eAAAA;;;wBAxEN;wBACmD;sBAOnD;+BAEiE;;;;;;;;;;;;;;;AAejE,MAAMD,6BAA6B;AA+CnC,IAAA,AAAMC,iBAAN,MAAMA;;;;IACMC,SAAS,IAAIC,cAAM,CAACF,eAAeG,IAAI,EAAE;IAE1D,yDAAyD,GACzD,AAAQC,gBAA8B,KAAK;IAC3C,sEAAsE,GACtE,AAAQC,iBAAuD,KAAK;IAEpE;;;;;GAKC,GACD,AAAiBC,gBAAgB,IAAIC,MAAc;IAEnD,wEAAwE,GACxE,AAAQC,eAAqC,KAAK;IAElD,YACE,AAAiBC,gBAAuC,EACxD,AACiBC,OAAoC,EACrD,AAAiBC,QAAqB,CACtC;aAJiBF,mBAAAA;aAEAC,UAAAA;aACAC,WAAAA;IAChB;IAEH;;;;;;;;;;;;GAYC,GACDC,yBAA+B;QAC7B,IAAI,CAACR,aAAa,GAAG,IAAI,CAACS,kBAAkB;QAC5C,IAAI,IAAI,CAACH,OAAO,CAACI,eAAe,EAAE;YAChC,IAAI,CAACT,cAAc,GAAG,IAAI,CAACU,mBAAmB;QAChD;QACA,MAAMC,UAAU,IAAI,CAACP,gBAAgB,CAACQ,MAAM;QAC5C,KAAK,MAAMC,SAASF,QAAS;YAC3B,IAAI;gBACF,IAAI,CAACG,eAAe,CAACD;YACvB,EAAE,OAAOE,KAAK;gBACZ,IAAI,CAACnB,MAAM,CAACoB,IAAI,CACd,CAAC,gCAAgC,EAAEH,MAAMI,KAAK,CAAC,EAAE,EAAEJ,MAAMK,YAAY,CAAC,GAAG,CAAC,GACxE,GAAGH,eAAeI,QAAQJ,IAAIK,OAAO,GAAGC,OAAON,MAAM;YAE3D;QACF;QACA,IAAI,CAACnB,MAAM,CAAC0B,GAAG,CACb,CAAC,+BAA+B,EAAE5B,2BAA2B,EAAE,CAAC,GAC9D,CAAC,UAAU,EAAE,IAAI,CAACO,aAAa,CAACsB,IAAI,CAAC,CAAC,EAAEZ,QAAQa,MAAM,CAAC,CAAC,CAAC,GACzD,CAAC,SAAS,EAAEb,QAAQa,MAAM,GAAG,IAAI,CAACvB,aAAa,CAACsB,IAAI,CAAC,QAAQ,CAAC,GAC9D,CAAC,OAAO,EAAE,IAAI,CAAClB,OAAO,CAACI,eAAe,GAAG,SAAS,UAAU;IAElE;IAEA;;;;GAIC,GACD,MAAMgB,wBAAuC;QAC3C,IAAI,IAAI,CAACtB,YAAY,KAAK,MAAM;YAC9B,OAAO,IAAI,CAACA,YAAY;QAC1B;QACA,IAAI,CAACA,YAAY,GAAG,IAAI,CAACuB,KAAK;QAC9B,OAAO,IAAI,CAACvB,YAAY;IAC1B;IAEA;;;;GAIC,GACDwB,yBAA4C;QAC1C,OAAOC,MAAMC,IAAI,CAAC,IAAI,CAAC5B,aAAa;IACtC;IAEA,4EAA4E;IAC5E,eAAe;IACf,4EAA4E;IAE5E;;;;;GAKC,GACD,AAAQa,gBAAgBD,KAAyB,EAAQ;QACvD,IAAIA,MAAMiB,KAAK,EAAE;YACf,IAAI,CAAClC,MAAM,CAAC0B,GAAG,CACb,CAAC,yBAAyB,EAAET,MAAMI,KAAK,CAAC,EAAE,EAAEJ,MAAMK,YAAY,CAAC,CAAC,CAAC,GAC/D,CAAC,OAAO,EAAEL,MAAMkB,IAAI,CAAC,OAAO,EAAElB,MAAMmB,QAAQ,CAAC,EAAE,CAAC;YAEpD;QACF;QACA,IAAI,IAAI,CAACjC,aAAa,KAAK,MAAM;YAC/B,kEAAkE;YAClE,0DAA0D;YAC1D,uDAAuD;YACvD,sBAAsB;YACtB,MAAM,IAAIoB,MAAM;QAClB;QACA,MAAMc,eAAe,GAAGpB,MAAMI,KAAK,CAAC,EAAE,EAAEJ,MAAMK,YAAY,EAAE;QAC5D,MAAMgB,WAIF;YACFpC,MAAMe,MAAMK,YAAY;YACxBiB,MAAM;gBACJlB,OAAOJ,MAAMI,KAAK;gBAClBC,cAAcL,MAAMK,YAAY;gBAChCkB,YAAYvB,MAAMuB,UAAU;YAC9B;YACAC,MAAM;gBACJC,UAAU;gBACVC,SAAS;oBAAEC,MAAM;oBAAeC,OAAO;oBAAKC,QAAQ;gBAAI;gBACxDC,kBAAkB;oBAAEC,OAAO;oBAAKC,KAAK;gBAAK;gBAC1CC,cAAc;oBAAEF,OAAO;gBAAK;YAC9B;QACF;QACA,KAAK,IAAI,CAAC7C,aAAa,CAACgD,kBAAkB,CACxCd,cACA;YAAEe,SAASnC,MAAMkB,IAAI;YAAEkB,IAAIpC,MAAMmB,QAAQ;QAAC,GAC1CE;QAEF,IAAI,CAACjC,aAAa,CAACiD,GAAG,CAACjB;QACvB,IAAI,CAACrC,MAAM,CAAC0B,GAAG,CACb,CAAC,oBAAoB,EAAEW,aAAa,QAAQ,EAAEpB,MAAMkB,IAAI,CAAC,OAAO,EAAElB,MAAMmB,QAAQ,CAAC,EAAE,CAAC;IAExF;IAEA,4EAA4E;IAC5E,qBAAqB;IACrB,4EAA4E;IAE5E;;;;;;GAMC,GACD,AAAQxB,qBAA4B;QAClC,OAAO,IAAI2C,aAAK,CAACzD,4BAA4B;YAC3C0D,YAAY,IAAI,CAACC,yBAAyB;YAC1CC,mBAAmB;gBACjBhB,UAAU;gBACVC,SAAS;oBAAEC,MAAM;oBAAeC,OAAO;oBAAKC,QAAQ;gBAAI;gBACxDC,kBAAkB;oBAAEC,OAAO;oBAAKC,KAAK;gBAAK;gBAC1CC,cAAc;oBAAEF,OAAO;gBAAK;YAC9B;YACAW,QAAQ,IAAI,CAAClD,OAAO,CAAC+C,UAAU,CAACI,SAAS;YACzCC,qBAAqB;YACrB,yDAAyD;YACzD,uDAAuD;YACvD,gCAAgC;YAChCC,kBAAkB;QACpB;IACF;IAEQhD,sBAAqD;QAC3D,OAAO,IAAIiD,cAAM,CACfjE,4BACA,OAAOkE,MAAQ,IAAI,CAACC,mBAAmB,CAACD,MACxC;YACER,YAAY,IAAI,CAACU,uBAAuB;YACxCP,QAAQ,IAAI,CAAClD,OAAO,CAAC+C,UAAU,CAACI,SAAS;YACzCO,aAAa;QACf;IAEJ;IAEA,MAAcF,oBAAoBD,GAA+B,EAAiB;QAChF,MAAM,EAAE3C,KAAK,EAAEC,YAAY,EAAEkB,UAAU,EAAE,GAAGwB,IAAIzB,IAAI;QACpD,IAAI,OAAOlB,UAAU,YAAYA,MAAMO,MAAM,KAAK,GAAG;YACnD,MAAM,IAAIL,MAAM;QAClB;QACA,IAAI,OAAOD,iBAAiB,YAAYA,aAAaM,MAAM,KAAK,GAAG;YACjE,MAAM,IAAIL,MAAM;QAClB;QACA,IAAI,OAAOiB,eAAe,YAAYA,WAAWZ,MAAM,KAAK,GAAG;YAC7D,MAAM,IAAIL,MAAM;QAClB;QAEA,MAAM6C,cACJ,OAAOJ,IAAIK,SAAS,KAAK,YAAYC,OAAOC,QAAQ,CAACP,IAAIK,SAAS,IAC9D,IAAIG,KAAKR,IAAIK,SAAS,EAAEI,WAAW,KACnC,IAAID,OAAOC,WAAW;QAC5B,MAAMC,SAAwB;YAC5BC,WAAW;YACXrD;YACA8C;YACAQ,oBAAoBnD,OAAOuC,IAAIa,EAAE,IAAI;QACvC;QACA,MAAMC,YAAY,MAAM,IAAI,CAACpE,QAAQ,CAACqE,MAAM,CAAC1D,OAAOqD;QACpD,IAAI,CAAC1E,MAAM,CAAC0B,GAAG,CACb,CAAC,eAAe,EAAEL,MAAM,EAAE,EAAEC,aAAa,CAAC,CAAC,GACzC,CAAC,QAAQ,EAAEkB,WAAW,eAAe,EAAEsC,UAAUD,EAAE,CAAC,QAAQ,EAAEC,UAAUE,MAAM,EAAE;IAEtF;IAEQvB,4BAAqD;QAC3D,OAAO;YACLwB,MAAM,IAAI,CAACxE,OAAO,CAAC+C,UAAU,CAACyB,IAAI;YAClCC,MAAM,IAAI,CAACzE,OAAO,CAAC+C,UAAU,CAAC0B,IAAI;YAClCC,UAAU,IAAI,CAAC1E,OAAO,CAAC+C,UAAU,CAAC2B,QAAQ;YAC1CC,UAAU,IAAI,CAAC3E,OAAO,CAAC+C,UAAU,CAAC4B,QAAQ;YAC1CC,IAAI,IAAI,CAAC5E,OAAO,CAAC+C,UAAU,CAAC6B,EAAE;YAC9B,GAAI,IAAI,CAAC5E,OAAO,CAAC+C,UAAU,CAAC8B,GAAG,GAAG;gBAAEA,KAAK;YAAK,IAAI,CAAC,CAAC;YACpDC,oBAAoB;YACpBC,sBAAsB;QACxB;IACF;IAEQtB,0BAAmD;QACzD,OAAO;YACLe,MAAM,IAAI,CAACxE,OAAO,CAAC+C,UAAU,CAACyB,IAAI;YAClCC,MAAM,IAAI,CAACzE,OAAO,CAAC+C,UAAU,CAAC0B,IAAI;YAClCC,UAAU,IAAI,CAAC1E,OAAO,CAAC+C,UAAU,CAAC2B,QAAQ;YAC1CC,UAAU,IAAI,CAAC3E,OAAO,CAAC+C,UAAU,CAAC4B,QAAQ;YAC1CC,IAAI,IAAI,CAAC5E,OAAO,CAAC+C,UAAU,CAAC6B,EAAE;YAC9B,GAAI,IAAI,CAAC5E,OAAO,CAAC+C,UAAU,CAAC8B,GAAG,GAAG;gBAAEA,KAAK;YAAK,IAAI,CAAC,CAAC;YACpDE,sBAAsB;YACtBC,kBAAkB;QACpB;IACF;IAEA,4EAA4E;IAC5E,QAAQ;IACR,4EAA4E;IAE5E;;;;;;GAMC,GACD,MAAc3D,QAAuB;QACnC,IAAI,IAAI,CAAC1B,cAAc,KAAK,MAAM;YAChC,IAAI;gBACF,MAAM,IAAI,CAACA,cAAc,CAAC0B,KAAK;YACjC,EAAE,OAAOX,KAAK;gBACZ,IAAI,CAACnB,MAAM,CAACoB,IAAI,CACd,CAAC,8BAA8B,EAAED,eAAeI,QAAQJ,IAAIK,OAAO,GAAGC,OAAON,MAAM;YAEvF;YACA,IAAI,CAACf,cAAc,GAAG;QACxB;QACA,IAAI,IAAI,CAACD,aAAa,KAAK,MAAM;YAC/B,KAAK,MAAMuF,OAAO,IAAI,CAACrF,aAAa,CAAE;gBACpC,IAAI;oBACF,MAAM,IAAI,CAACF,aAAa,CAACwF,kBAAkB,CAACD;gBAC9C,EAAE,OAAOvE,KAAK;oBACZ,IAAI,CAACnB,MAAM,CAACoB,IAAI,CACd,CAAC,oBAAoB,EAAEsE,IAAI,WAAW,EACpCvE,eAAeI,QAAQJ,IAAIK,OAAO,GAAGC,OAAON,MAC5C;gBAEN;YACF;YACA,IAAI;gBACF,MAAM,IAAI,CAAChB,aAAa,CAAC2B,KAAK;YAChC,EAAE,OAAOX,KAAK;gBACZ,IAAI,CAACnB,MAAM,CAACoB,IAAI,CACd,CAAC,6BAA6B,EAAED,eAAeI,QAAQJ,IAAIK,OAAO,GAAGC,OAAON,MAAM;YAEtF;YACA,IAAI,CAAChB,aAAa,GAAG;QACvB;IACF;AACF"}
@@ -1,5 +1,5 @@
1
1
  import { OnApplicationBootstrap, OnApplicationShutdown } from '@nestjs/common';
2
- import { BatchScheduleRegistry } from '@nest-batch/core';
2
+ import { BatchScheduleRegistry, JobLauncher } from '@nest-batch/core';
3
3
  import { type ResolvedBullMqModuleOptions } from './module-options';
4
4
  /**
5
5
  * The single BullMQ queue name used by the schedule service. We
@@ -15,6 +15,10 @@ import { type ResolvedBullMqModuleOptions } from './module-options';
15
15
  * convention (`'nest-batch-work'`).
16
16
  */
17
17
  export declare const BULLMQ_SCHEDULE_QUEUE_NAME = "nest-batch-schedule";
18
+ export interface BullmqSchedulePayload {
19
+ readonly jobId: string;
20
+ readonly methodName: string;
21
+ }
18
22
  /**
19
23
  * `BullmqScheduleService` — the runtime scheduler for
20
24
  * `@BatchScheduled` entries.
@@ -28,9 +32,10 @@ export declare const BULLMQ_SCHEDULE_QUEUE_NAME = "nest-batch-schedule";
28
32
  * 2. BullMQ's `upsertJobScheduler` internally fires the
29
33
  * schedule at the configured cron time. Each fire enqueues a
30
34
  * job into the schedule queue (named after the schedule
31
- * entry's method). A separate `Worker` (the one owned by
32
- * `BullmqRuntimeService` if `autoStartWorker` is `true`)
33
- * processes the jobs.
35
+ * entry's method). When `autoStartWorker` is `true`, this
36
+ * service also starts a schedule-queue worker that bridges
37
+ * `{ jobId, methodName }` into `JobLauncher.launch(jobId,
38
+ * params)`.
34
39
  * 3. `OnApplicationShutdown` removes every installed scheduler
35
40
  * (via `queue.removeJobScheduler`) and closes the queue.
36
41
  * Removal is best-effort: a partial failure logs a warning
@@ -42,10 +47,11 @@ export declare const BULLMQ_SCHEDULE_QUEUE_NAME = "nest-batch-schedule";
42
47
  * and the worker bridge. Mixing scheduler concerns in would
43
48
  * bloat its surface and couple two lifecycles that happen to
44
49
  * share a Redis client but are otherwise independent.
45
- * - The scheduler does NOT need a `Worker`; the runtime service
46
- * does. A separate service can run with `autoStartWorker:
47
- * false` cleanly (a launcher-only deployment that still wants
48
- * cron schedules to fire).
50
+ * - The schedule queue needs a different worker contract from
51
+ * the runtime work queue: schedule payloads identify a job to
52
+ * launch, while work payloads identify an already-created
53
+ * execution/step. Keeping the bridge here avoids teaching
54
+ * `BullmqRuntimeService` a second payload shape.
49
55
  * - The schedule service owns its own `Queue` (the schedule
50
56
  * queue) so cron jobs are not interleaved with manually-launched
51
57
  * jobs. They share the same `keyPrefix` so the host's Redis
@@ -54,9 +60,12 @@ export declare const BULLMQ_SCHEDULE_QUEUE_NAME = "nest-batch-schedule";
54
60
  export declare class BullmqScheduleService implements OnApplicationBootstrap, OnApplicationShutdown {
55
61
  private readonly scheduleRegistry;
56
62
  private readonly options;
63
+ private readonly launcher;
57
64
  private readonly logger;
58
65
  /** BullMQ queue for the scheduler (producer side only). */
59
66
  private scheduleQueue;
67
+ /** BullMQ worker that turns schedule fires into real batch launches. */
68
+ private scheduleWorker;
60
69
  /**
61
70
  * Every schedule key installed during `onApplicationBootstrap`.
62
71
  * Tracked so the shutdown path can `removeJobScheduler` for
@@ -66,7 +75,7 @@ export declare class BullmqScheduleService implements OnApplicationBootstrap, On
66
75
  private readonly installedKeys;
67
76
  /** Promise-chain lock for the close path. Mirrors the runtime service. */
68
77
  private closePromise;
69
- constructor(scheduleRegistry: BatchScheduleRegistry, options: ResolvedBullMqModuleOptions);
78
+ constructor(scheduleRegistry: BatchScheduleRegistry, options: ResolvedBullMqModuleOptions, launcher: JobLauncher);
70
79
  /**
71
80
  * Walk the registry and install every non-inert entry as a
72
81
  * BullMQ repeating job. Runs AFTER the `BatchBootstrapper` has
@@ -108,7 +117,10 @@ export declare class BullmqScheduleService implements OnApplicationBootstrap, On
108
117
  * (`maxRetriesPerRequest: 1`).
109
118
  */
110
119
  private buildScheduleQueue;
120
+ private buildScheduleWorker;
121
+ private processScheduleFire;
111
122
  private producerConnectionOptions;
123
+ private workerConnectionOptions;
112
124
  /**
113
125
  * Close the schedule queue. `removeJobScheduler` is called
114
126
  * first for every installed key so the next run of the host
@@ -1 +1 @@
1
- {"version":3,"file":"bullmq-schedule.service.d.ts","sourceRoot":"","sources":["../../src/bullmq-schedule.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,sBAAsB,EACtB,qBAAqB,EACtB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAA2B,qBAAqB,EAA2B,MAAM,kBAAkB,CAAC;AAE3G,OAAO,EAAyB,KAAK,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAE3F;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,0BAA0B,wBAAwB,CAAC;AAEhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,qBACa,qBAAsB,YAAW,sBAAsB,EAAE,qBAAqB;IAkBvF,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAEjC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAnB1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0C;IAEjE,2DAA2D;IAC3D,OAAO,CAAC,aAAa,CAAsB;IAE3C;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IAEnD,0EAA0E;IAC1E,OAAO,CAAC,YAAY,CAA8B;gBAG/B,gBAAgB,EAAE,qBAAqB,EAEvC,OAAO,EAAE,2BAA2B;IAGvD;;;;;;;;;;;;OAYG;IACH,sBAAsB,IAAI,IAAI;IAoB9B;;;;OAIG;IACG,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5C;;;;OAIG;IACH,sBAAsB,IAAI,SAAS,MAAM,EAAE;IAQ3C;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IA6CvB;;;;;;OAMG;IACH,OAAO,CAAC,kBAAkB;IAkB1B,OAAO,CAAC,yBAAyB;IAiBjC;;;;;;OAMG;YACW,KAAK;CAuBpB"}
1
+ {"version":3,"file":"bullmq-schedule.service.d.ts","sourceRoot":"","sources":["../../src/bullmq-schedule.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,sBAAsB,EACtB,qBAAqB,EACtB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,qBAAqB,EACrB,WAAW,EAGZ,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAyB,KAAK,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAE3F;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,0BAA0B,wBAAwB,CAAC;AAEhE,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,qBACa,qBAAsB,YAAW,sBAAsB,EAAE,qBAAqB;IAoBvF,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAEjC,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAtB3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0C;IAEjE,2DAA2D;IAC3D,OAAO,CAAC,aAAa,CAAsB;IAC3C,wEAAwE;IACxE,OAAO,CAAC,cAAc,CAA8C;IAEpE;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IAEnD,0EAA0E;IAC1E,OAAO,CAAC,YAAY,CAA8B;gBAG/B,gBAAgB,EAAE,qBAAqB,EAEvC,OAAO,EAAE,2BAA2B,EACpC,QAAQ,EAAE,WAAW;IAGxC;;;;;;;;;;;;OAYG;IACH,sBAAsB,IAAI,IAAI;IAwB9B;;;;OAIG;IACG,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5C;;;;OAIG;IACH,sBAAsB,IAAI,SAAS,MAAM,EAAE;IAQ3C;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IA6CvB;;;;;;OAMG;IACH,OAAO,CAAC,kBAAkB;IAkB1B,OAAO,CAAC,mBAAmB;YAYb,mBAAmB;IAyBjC,OAAO,CAAC,yBAAyB;IAajC,OAAO,CAAC,uBAAuB;IAiB/B;;;;;;OAMG;YACW,KAAK;CAiCpB"}
@@ -38,8 +38,10 @@ const BULLMQ_SCHEDULE_QUEUE_NAME = 'nest-batch-schedule';
38
38
  let BullmqScheduleService = class BullmqScheduleService {
39
39
  scheduleRegistry;
40
40
  options;
41
+ launcher;
41
42
  logger = new _common.Logger(BullmqScheduleService.name);
42
43
  /** BullMQ queue for the scheduler (producer side only). */ scheduleQueue = null;
44
+ /** BullMQ worker that turns schedule fires into real batch launches. */ scheduleWorker = null;
43
45
  /**
44
46
  * Every schedule key installed during `onApplicationBootstrap`.
45
47
  * Tracked so the shutdown path can `removeJobScheduler` for
@@ -47,9 +49,10 @@ let BullmqScheduleService = class BullmqScheduleService {
47
49
  * order-independent.
48
50
  */ installedKeys = new Set();
49
51
  /** Promise-chain lock for the close path. Mirrors the runtime service. */ closePromise = null;
50
- constructor(scheduleRegistry, options){
52
+ constructor(scheduleRegistry, options, launcher){
51
53
  this.scheduleRegistry = scheduleRegistry;
52
54
  this.options = options;
55
+ this.launcher = launcher;
53
56
  }
54
57
  /**
55
58
  * Walk the registry and install every non-inert entry as a
@@ -65,6 +68,9 @@ let BullmqScheduleService = class BullmqScheduleService {
65
68
  * for the valid ones.
66
69
  */ onApplicationBootstrap() {
67
70
  this.scheduleQueue = this.buildScheduleQueue();
71
+ if (this.options.autoStartWorker) {
72
+ this.scheduleWorker = this.buildScheduleWorker();
73
+ }
68
74
  const entries = this.scheduleRegistry.getAll();
69
75
  for (const entry of entries){
70
76
  try {
@@ -73,7 +79,7 @@ let BullmqScheduleService = class BullmqScheduleService {
73
79
  this.logger.warn(`Failed to install schedule for "${entry.jobId}::${entry.methodName}": ` + `${err instanceof Error ? err.message : String(err)}`);
74
80
  }
75
81
  }
76
- this.logger.log(`BullmqScheduleService started: queue="${BULLMQ_SCHEDULE_QUEUE_NAME}" ` + `schedules=${this.installedKeys.size}/${entries.length} ` + `(skipped=${entries.length - this.installedKeys.size} inert)`);
82
+ this.logger.log(`BullmqScheduleService started: queue="${BULLMQ_SCHEDULE_QUEUE_NAME}" ` + `schedules=${this.installedKeys.size}/${entries.length} ` + `(skipped=${entries.length - this.installedKeys.size} inert) ` + `worker=${this.options.autoStartWorker ? 'auto' : 'manual'}`);
77
83
  }
78
84
  /**
79
85
  * Tear down every installed scheduler and close the schedule
@@ -178,6 +184,31 @@ let BullmqScheduleService = class BullmqScheduleService {
178
184
  skipVersionCheck: true
179
185
  });
180
186
  }
187
+ buildScheduleWorker() {
188
+ return new _bullmq.Worker(BULLMQ_SCHEDULE_QUEUE_NAME, async (job)=>this.processScheduleFire(job), {
189
+ connection: this.workerConnectionOptions(),
190
+ prefix: this.options.connection.keyPrefix,
191
+ concurrency: 1
192
+ });
193
+ }
194
+ async processScheduleFire(job) {
195
+ const { jobId, methodName } = job.data;
196
+ if (typeof jobId !== 'string' || jobId.length === 0) {
197
+ throw new Error('[BullmqScheduleService] schedule payload is missing jobId');
198
+ }
199
+ if (typeof methodName !== 'string' || methodName.length === 0) {
200
+ throw new Error('[BullmqScheduleService] schedule payload is missing methodName');
201
+ }
202
+ const scheduledAt = typeof job.timestamp === 'number' && Number.isFinite(job.timestamp) ? new Date(job.timestamp).toISOString() : new Date().toISOString();
203
+ const params = {
204
+ scheduled: true,
205
+ scheduleName: methodName,
206
+ scheduledAt,
207
+ scheduleQueueJobId: String(job.id ?? '')
208
+ };
209
+ const execution = await this.launcher.launch(jobId, params);
210
+ this.logger.log(`Fired schedule ${jobId}::${methodName} -> execution=${execution.id} status=${execution.status}`);
211
+ }
181
212
  producerConnectionOptions() {
182
213
  return {
183
214
  host: this.options.connection.host,
@@ -192,6 +223,20 @@ let BullmqScheduleService = class BullmqScheduleService {
192
223
  maxRetriesPerRequest: 1
193
224
  };
194
225
  }
226
+ workerConnectionOptions() {
227
+ return {
228
+ host: this.options.connection.host,
229
+ port: this.options.connection.port,
230
+ password: this.options.connection.password,
231
+ username: this.options.connection.username,
232
+ db: this.options.connection.db,
233
+ ...this.options.connection.tls ? {
234
+ tls: true
235
+ } : {},
236
+ maxRetriesPerRequest: null,
237
+ enableReadyCheck: false
238
+ };
239
+ }
195
240
  // -------------------------------------------------------------------------
196
241
  // Close
197
242
  // -------------------------------------------------------------------------
@@ -202,6 +247,14 @@ let BullmqScheduleService = class BullmqScheduleService {
202
247
  * best-effort: a failure on one key does not prevent the
203
248
  * others from being removed.
204
249
  */ async close() {
250
+ if (this.scheduleWorker !== null) {
251
+ try {
252
+ await this.scheduleWorker.close();
253
+ } catch (err) {
254
+ this.logger.warn(`Schedule worker close failed: ${err instanceof Error ? err.message : String(err)}`);
255
+ }
256
+ this.scheduleWorker = null;
257
+ }
205
258
  if (this.scheduleQueue !== null) {
206
259
  for (const key of this.installedKeys){
207
260
  try {
@@ -225,7 +278,8 @@ BullmqScheduleService = _ts_decorate([
225
278
  _ts_metadata("design:type", Function),
226
279
  _ts_metadata("design:paramtypes", [
227
280
  typeof _core.BatchScheduleRegistry === "undefined" ? Object : _core.BatchScheduleRegistry,
228
- typeof ResolvedBullMqModuleOptions === "undefined" ? Object : ResolvedBullMqModuleOptions
281
+ typeof ResolvedBullMqModuleOptions === "undefined" ? Object : ResolvedBullMqModuleOptions,
282
+ typeof _core.JobLauncher === "undefined" ? Object : _core.JobLauncher
229
283
  ])
230
284
  ], BullmqScheduleService);
231
285
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/bullmq-schedule.service.ts"],"sourcesContent":["import {\n Inject,\n Injectable,\n Logger,\n OnApplicationBootstrap,\n OnApplicationShutdown,\n} from '@nestjs/common';\nimport { Queue, type JobsOptions } from 'bullmq';\n\nimport { BATCH_SCHEDULE_REGISTRY, BatchScheduleRegistry, type BatchScheduleEntry } from '@nest-batch/core';\n\nimport { BULLMQ_MODULE_OPTIONS, type ResolvedBullMqModuleOptions } from './module-options';\n\n/**\n * The single BullMQ queue name used by the schedule service. We\n * intentionally use a DIFFERENT queue from the runtime service's\n * `BULLMQ_QUEUE_NAME` so cron-triggered jobs and ad-hoc\n * `launch()`-triggered jobs are inspectable in isolation (and so\n * the schedule-removal path on shutdown can tear them down\n * without touching the runtime work queue).\n *\n * BullMQ 5 rejects queue names that contain a colon (`:`) because\n * it is the path separator in the key layout. We use a hyphen\n * (`-`) instead, matching the existing `BULLMQ_QUEUE_NAME`\n * convention (`'nest-batch-work'`).\n */\nexport const BULLMQ_SCHEDULE_QUEUE_NAME = 'nest-batch-schedule';\n\n/**\n * `BullmqScheduleService` — the runtime scheduler for\n * `@BatchScheduled` entries.\n *\n * Lifecycle:\n * 1. `OnApplicationBootstrap` walks the `BatchScheduleRegistry`\n * and, for every entry with `inert: false`, registers a\n * BullMQ repeating job via `queue.upsertJobScheduler(...)`.\n * Entries with `inert: true` are logged and skipped — that\n * is the only place the inert flag is consulted.\n * 2. BullMQ's `upsertJobScheduler` internally fires the\n * schedule at the configured cron time. Each fire enqueues a\n * job into the schedule queue (named after the schedule\n * entry's method). A separate `Worker` (the one owned by\n * `BullmqRuntimeService` if `autoStartWorker` is `true`)\n * processes the jobs.\n * 3. `OnApplicationShutdown` removes every installed scheduler\n * (via `queue.removeJobScheduler`) and closes the queue.\n * Removal is best-effort: a partial failure logs a warning\n * but does not block the rest of the shutdown.\n *\n * Why a dedicated service (not a method on `BullmqRuntimeService`)?\n * - The runtime service is `IExecutionStrategy`-facing; it\n * knows about `JobExecution`, the in-process launch contract,\n * and the worker bridge. Mixing scheduler concerns in would\n * bloat its surface and couple two lifecycles that happen to\n * share a Redis client but are otherwise independent.\n * - The scheduler does NOT need a `Worker`; the runtime service\n * does. A separate service can run with `autoStartWorker:\n * false` cleanly (a launcher-only deployment that still wants\n * cron schedules to fire).\n * - The schedule service owns its own `Queue` (the schedule\n * queue) so cron jobs are not interleaved with manually-launched\n * jobs. They share the same `keyPrefix` so the host's Redis\n * namespace policy still applies.\n */\n@Injectable()\nexport class BullmqScheduleService implements OnApplicationBootstrap, OnApplicationShutdown {\n private readonly logger = new Logger(BullmqScheduleService.name);\n\n /** BullMQ queue for the scheduler (producer side only). */\n private scheduleQueue: Queue | null = null;\n\n /**\n * Every schedule key installed during `onApplicationBootstrap`.\n * Tracked so the shutdown path can `removeJobScheduler` for\n * each one deterministically. A `Set` keeps the test assertions\n * order-independent.\n */\n private readonly installedKeys = new Set<string>();\n\n /** Promise-chain lock for the close path. Mirrors the runtime service. */\n private closePromise: Promise<void> | null = null;\n\n constructor(\n private readonly scheduleRegistry: BatchScheduleRegistry,\n @Inject(BULLMQ_MODULE_OPTIONS)\n private readonly options: ResolvedBullMqModuleOptions,\n ) {}\n\n /**\n * Walk the registry and install every non-inert entry as a\n * BullMQ repeating job. Runs AFTER the `BatchBootstrapper` has\n * populated the registry (both hooks are on\n * `OnApplicationBootstrap`, but Nest calls them in\n * provider-registration order; the bootstrapper is registered\n * before this service by `BullmqBatchModule.forRoot()`).\n *\n * Each entry is wrapped in a per-entry `try` so a single bad\n * schedule does not abort the rest of the installation. Bad\n * schedules are logged and skipped — the runtime keeps running\n * for the valid ones.\n */\n onApplicationBootstrap(): void {\n this.scheduleQueue = this.buildScheduleQueue();\n const entries = this.scheduleRegistry.getAll();\n for (const entry of entries) {\n try {\n this.installSchedule(entry);\n } catch (err) {\n this.logger.warn(\n `Failed to install schedule for \"${entry.jobId}::${entry.methodName}\": ` +\n `${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n this.logger.log(\n `BullmqScheduleService started: queue=\"${BULLMQ_SCHEDULE_QUEUE_NAME}\" ` +\n `schedules=${this.installedKeys.size}/${entries.length} ` +\n `(skipped=${entries.length - this.installedKeys.size} inert)`,\n );\n }\n\n /**\n * Tear down every installed scheduler and close the schedule\n * queue. Idempotent: a second `onApplicationShutdown` short-\n * circuits to the first close's promise.\n */\n async onApplicationShutdown(): Promise<void> {\n if (this.closePromise !== null) {\n return this.closePromise;\n }\n this.closePromise = this.close();\n return this.closePromise;\n }\n\n /**\n * Installed scheduler keys, in insertion order. Exposed for\n * tests and diagnostics. Read-only: callers MUST NOT mutate\n * the returned array.\n */\n installedSchedulerKeys(): readonly string[] {\n return Array.from(this.installedKeys);\n }\n\n // -------------------------------------------------------------------------\n // Installation\n // -------------------------------------------------------------------------\n\n /**\n * Install a single entry as a BullMQ repeating job. Skips\n * inert entries (the runtime honours the inert flag by NOT\n * calling `upsertJobScheduler` for them). Throws on\n * installation failure so the caller can log + continue.\n */\n private installSchedule(entry: BatchScheduleEntry): void {\n if (entry.inert) {\n this.logger.log(\n `Skipping inert schedule: ${entry.jobId}::${entry.methodName} ` +\n `(cron=\"${entry.cron}\", tz=\"${entry.timezone}\")`,\n );\n return;\n }\n if (this.scheduleQueue === null) {\n // Defensive: should never happen because `onApplicationBootstrap`\n // builds the queue before iterating entries, but a future\n // refactor that calls `installSchedule` from elsewhere\n // should fail loudly.\n throw new Error('[BullmqScheduleService] scheduleQueue is null');\n }\n const schedulerKey = `${entry.jobId}::${entry.methodName}`;\n const template: {\n name: string;\n data: Record<string, unknown>;\n opts: JobsOptions;\n } = {\n name: entry.methodName,\n data: { jobId: entry.jobId, methodName: entry.methodName },\n opts: {\n attempts: 3,\n backoff: { type: 'exponential', delay: 100, jitter: 0.5 },\n removeOnComplete: { count: 100, age: 3600 },\n removeOnFail: { count: 1000 },\n },\n };\n void this.scheduleQueue.upsertJobScheduler(\n schedulerKey,\n { pattern: entry.cron, tz: entry.timezone },\n template,\n );\n this.installedKeys.add(schedulerKey);\n this.logger.log(\n `Installed schedule: ${schedulerKey} (cron=\"${entry.cron}\", tz=\"${entry.timezone}\")`,\n );\n }\n\n // -------------------------------------------------------------------------\n // Queue construction\n // -------------------------------------------------------------------------\n\n /**\n * Build the producer-side BullMQ queue for the scheduler. The\n * connection tuning mirrors the runtime service's producer\n * options: fail-fast on Redis-down (`enableOfflineQueue:\n * false`) and a tight per-request retry budget\n * (`maxRetriesPerRequest: 1`).\n */\n private buildScheduleQueue(): Queue {\n return new Queue(BULLMQ_SCHEDULE_QUEUE_NAME, {\n connection: this.producerConnectionOptions(),\n defaultJobOptions: {\n attempts: 3,\n backoff: { type: 'exponential', delay: 100, jitter: 0.5 },\n removeOnComplete: { count: 100, age: 3600 },\n removeOnFail: { count: 1000 },\n },\n prefix: this.options.connection.keyPrefix,\n skipWaitingForReady: true,\n // Mirrors the runtime service: skip the constructor-time\n // version probe so the queue does not throw on a Redis\n // client that is not yet ready.\n skipVersionCheck: true,\n });\n }\n\n private producerConnectionOptions(): Record<string, unknown> {\n return {\n host: this.options.connection.host,\n port: this.options.connection.port,\n password: this.options.connection.password,\n username: this.options.connection.username,\n db: this.options.connection.db,\n ...(this.options.connection.tls ? { tls: true } : {}),\n enableOfflineQueue: false,\n maxRetriesPerRequest: 1,\n };\n }\n\n // -------------------------------------------------------------------------\n // Close\n // -------------------------------------------------------------------------\n\n /**\n * Close the schedule queue. `removeJobScheduler` is called\n * first for every installed key so the next run of the host\n * app does not inherit leftover schedulers. Each removal is\n * best-effort: a failure on one key does not prevent the\n * others from being removed.\n */\n private async close(): Promise<void> {\n if (this.scheduleQueue !== null) {\n for (const key of this.installedKeys) {\n try {\n await this.scheduleQueue.removeJobScheduler(key);\n } catch (err) {\n this.logger.warn(\n `removeJobScheduler(\"${key}\") failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n try {\n await this.scheduleQueue.close();\n } catch (err) {\n this.logger.warn(\n `Schedule queue close failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n this.scheduleQueue = null;\n }\n }\n}\n"],"names":["BULLMQ_SCHEDULE_QUEUE_NAME","BullmqScheduleService","logger","Logger","name","scheduleQueue","installedKeys","Set","closePromise","scheduleRegistry","options","onApplicationBootstrap","buildScheduleQueue","entries","getAll","entry","installSchedule","err","warn","jobId","methodName","Error","message","String","log","size","length","onApplicationShutdown","close","installedSchedulerKeys","Array","from","inert","cron","timezone","schedulerKey","template","data","opts","attempts","backoff","type","delay","jitter","removeOnComplete","count","age","removeOnFail","upsertJobScheduler","pattern","tz","add","Queue","connection","producerConnectionOptions","defaultJobOptions","prefix","keyPrefix","skipWaitingForReady","skipVersionCheck","host","port","password","username","db","tls","enableOfflineQueue","maxRetriesPerRequest","key","removeJobScheduler"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/bullmq-schedule.service.ts"],"sourcesContent":["import {\n Inject,\n Injectable,\n Logger,\n OnApplicationBootstrap,\n OnApplicationShutdown,\n} from '@nestjs/common';\nimport { Queue, Worker, type Job, type JobsOptions } from 'bullmq';\n\nimport {\n BatchScheduleRegistry,\n JobLauncher,\n type BatchScheduleEntry,\n type JobParameters,\n} from '@nest-batch/core';\n\nimport { BULLMQ_MODULE_OPTIONS, type ResolvedBullMqModuleOptions } from './module-options';\n\n/**\n * The single BullMQ queue name used by the schedule service. We\n * intentionally use a DIFFERENT queue from the runtime service's\n * `BULLMQ_QUEUE_NAME` so cron-triggered jobs and ad-hoc\n * `launch()`-triggered jobs are inspectable in isolation (and so\n * the schedule-removal path on shutdown can tear them down\n * without touching the runtime work queue).\n *\n * BullMQ 5 rejects queue names that contain a colon (`:`) because\n * it is the path separator in the key layout. We use a hyphen\n * (`-`) instead, matching the existing `BULLMQ_QUEUE_NAME`\n * convention (`'nest-batch-work'`).\n */\nexport const BULLMQ_SCHEDULE_QUEUE_NAME = 'nest-batch-schedule';\n\nexport interface BullmqSchedulePayload {\n readonly jobId: string;\n readonly methodName: string;\n}\n\n/**\n * `BullmqScheduleService` — the runtime scheduler for\n * `@BatchScheduled` entries.\n *\n * Lifecycle:\n * 1. `OnApplicationBootstrap` walks the `BatchScheduleRegistry`\n * and, for every entry with `inert: false`, registers a\n * BullMQ repeating job via `queue.upsertJobScheduler(...)`.\n * Entries with `inert: true` are logged and skipped — that\n * is the only place the inert flag is consulted.\n * 2. BullMQ's `upsertJobScheduler` internally fires the\n * schedule at the configured cron time. Each fire enqueues a\n * job into the schedule queue (named after the schedule\n * entry's method). When `autoStartWorker` is `true`, this\n * service also starts a schedule-queue worker that bridges\n * `{ jobId, methodName }` into `JobLauncher.launch(jobId,\n * params)`.\n * 3. `OnApplicationShutdown` removes every installed scheduler\n * (via `queue.removeJobScheduler`) and closes the queue.\n * Removal is best-effort: a partial failure logs a warning\n * but does not block the rest of the shutdown.\n *\n * Why a dedicated service (not a method on `BullmqRuntimeService`)?\n * - The runtime service is `IExecutionStrategy`-facing; it\n * knows about `JobExecution`, the in-process launch contract,\n * and the worker bridge. Mixing scheduler concerns in would\n * bloat its surface and couple two lifecycles that happen to\n * share a Redis client but are otherwise independent.\n * - The schedule queue needs a different worker contract from\n * the runtime work queue: schedule payloads identify a job to\n * launch, while work payloads identify an already-created\n * execution/step. Keeping the bridge here avoids teaching\n * `BullmqRuntimeService` a second payload shape.\n * - The schedule service owns its own `Queue` (the schedule\n * queue) so cron jobs are not interleaved with manually-launched\n * jobs. They share the same `keyPrefix` so the host's Redis\n * namespace policy still applies.\n */\n@Injectable()\nexport class BullmqScheduleService implements OnApplicationBootstrap, OnApplicationShutdown {\n private readonly logger = new Logger(BullmqScheduleService.name);\n\n /** BullMQ queue for the scheduler (producer side only). */\n private scheduleQueue: Queue | null = null;\n /** BullMQ worker that turns schedule fires into real batch launches. */\n private scheduleWorker: Worker<BullmqSchedulePayload> | null = null;\n\n /**\n * Every schedule key installed during `onApplicationBootstrap`.\n * Tracked so the shutdown path can `removeJobScheduler` for\n * each one deterministically. A `Set` keeps the test assertions\n * order-independent.\n */\n private readonly installedKeys = new Set<string>();\n\n /** Promise-chain lock for the close path. Mirrors the runtime service. */\n private closePromise: Promise<void> | null = null;\n\n constructor(\n private readonly scheduleRegistry: BatchScheduleRegistry,\n @Inject(BULLMQ_MODULE_OPTIONS)\n private readonly options: ResolvedBullMqModuleOptions,\n private readonly launcher: JobLauncher,\n ) {}\n\n /**\n * Walk the registry and install every non-inert entry as a\n * BullMQ repeating job. Runs AFTER the `BatchBootstrapper` has\n * populated the registry (both hooks are on\n * `OnApplicationBootstrap`, but Nest calls them in\n * provider-registration order; the bootstrapper is registered\n * before this service by `BullmqBatchModule.forRoot()`).\n *\n * Each entry is wrapped in a per-entry `try` so a single bad\n * schedule does not abort the rest of the installation. Bad\n * schedules are logged and skipped — the runtime keeps running\n * for the valid ones.\n */\n onApplicationBootstrap(): void {\n this.scheduleQueue = this.buildScheduleQueue();\n if (this.options.autoStartWorker) {\n this.scheduleWorker = this.buildScheduleWorker();\n }\n const entries = this.scheduleRegistry.getAll();\n for (const entry of entries) {\n try {\n this.installSchedule(entry);\n } catch (err) {\n this.logger.warn(\n `Failed to install schedule for \"${entry.jobId}::${entry.methodName}\": ` +\n `${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n this.logger.log(\n `BullmqScheduleService started: queue=\"${BULLMQ_SCHEDULE_QUEUE_NAME}\" ` +\n `schedules=${this.installedKeys.size}/${entries.length} ` +\n `(skipped=${entries.length - this.installedKeys.size} inert) ` +\n `worker=${this.options.autoStartWorker ? 'auto' : 'manual'}`,\n );\n }\n\n /**\n * Tear down every installed scheduler and close the schedule\n * queue. Idempotent: a second `onApplicationShutdown` short-\n * circuits to the first close's promise.\n */\n async onApplicationShutdown(): Promise<void> {\n if (this.closePromise !== null) {\n return this.closePromise;\n }\n this.closePromise = this.close();\n return this.closePromise;\n }\n\n /**\n * Installed scheduler keys, in insertion order. Exposed for\n * tests and diagnostics. Read-only: callers MUST NOT mutate\n * the returned array.\n */\n installedSchedulerKeys(): readonly string[] {\n return Array.from(this.installedKeys);\n }\n\n // -------------------------------------------------------------------------\n // Installation\n // -------------------------------------------------------------------------\n\n /**\n * Install a single entry as a BullMQ repeating job. Skips\n * inert entries (the runtime honours the inert flag by NOT\n * calling `upsertJobScheduler` for them). Throws on\n * installation failure so the caller can log + continue.\n */\n private installSchedule(entry: BatchScheduleEntry): void {\n if (entry.inert) {\n this.logger.log(\n `Skipping inert schedule: ${entry.jobId}::${entry.methodName} ` +\n `(cron=\"${entry.cron}\", tz=\"${entry.timezone}\")`,\n );\n return;\n }\n if (this.scheduleQueue === null) {\n // Defensive: should never happen because `onApplicationBootstrap`\n // builds the queue before iterating entries, but a future\n // refactor that calls `installSchedule` from elsewhere\n // should fail loudly.\n throw new Error('[BullmqScheduleService] scheduleQueue is null');\n }\n const schedulerKey = `${entry.jobId}::${entry.methodName}`;\n const template: {\n name: string;\n data: BullmqSchedulePayload;\n opts: JobsOptions;\n } = {\n name: entry.methodName,\n data: { jobId: entry.jobId, methodName: entry.methodName },\n opts: {\n attempts: 3,\n backoff: { type: 'exponential', delay: 100, jitter: 0.5 },\n removeOnComplete: { count: 100, age: 3600 },\n removeOnFail: { count: 1000 },\n },\n };\n void this.scheduleQueue.upsertJobScheduler(\n schedulerKey,\n { pattern: entry.cron, tz: entry.timezone },\n template,\n );\n this.installedKeys.add(schedulerKey);\n this.logger.log(\n `Installed schedule: ${schedulerKey} (cron=\"${entry.cron}\", tz=\"${entry.timezone}\")`,\n );\n }\n\n // -------------------------------------------------------------------------\n // Queue construction\n // -------------------------------------------------------------------------\n\n /**\n * Build the producer-side BullMQ queue for the scheduler. The\n * connection tuning mirrors the runtime service's producer\n * options: fail-fast on Redis-down (`enableOfflineQueue:\n * false`) and a tight per-request retry budget\n * (`maxRetriesPerRequest: 1`).\n */\n private buildScheduleQueue(): Queue {\n return new Queue(BULLMQ_SCHEDULE_QUEUE_NAME, {\n connection: this.producerConnectionOptions(),\n defaultJobOptions: {\n attempts: 3,\n backoff: { type: 'exponential', delay: 100, jitter: 0.5 },\n removeOnComplete: { count: 100, age: 3600 },\n removeOnFail: { count: 1000 },\n },\n prefix: this.options.connection.keyPrefix,\n skipWaitingForReady: true,\n // Mirrors the runtime service: skip the constructor-time\n // version probe so the queue does not throw on a Redis\n // client that is not yet ready.\n skipVersionCheck: true,\n });\n }\n\n private buildScheduleWorker(): Worker<BullmqSchedulePayload> {\n return new Worker<BullmqSchedulePayload>(\n BULLMQ_SCHEDULE_QUEUE_NAME,\n async (job) => this.processScheduleFire(job),\n {\n connection: this.workerConnectionOptions(),\n prefix: this.options.connection.keyPrefix,\n concurrency: 1,\n },\n );\n }\n\n private async processScheduleFire(job: Job<BullmqSchedulePayload>): Promise<void> {\n const { jobId, methodName } = job.data;\n if (typeof jobId !== 'string' || jobId.length === 0) {\n throw new Error('[BullmqScheduleService] schedule payload is missing jobId');\n }\n if (typeof methodName !== 'string' || methodName.length === 0) {\n throw new Error('[BullmqScheduleService] schedule payload is missing methodName');\n }\n\n const scheduledAt =\n typeof job.timestamp === 'number' && Number.isFinite(job.timestamp)\n ? new Date(job.timestamp).toISOString()\n : new Date().toISOString();\n const params: JobParameters = {\n scheduled: true,\n scheduleName: methodName,\n scheduledAt,\n scheduleQueueJobId: String(job.id ?? ''),\n };\n const execution = await this.launcher.launch(jobId, params);\n this.logger.log(\n `Fired schedule ${jobId}::${methodName} -> execution=${execution.id} status=${execution.status}`,\n );\n }\n\n private producerConnectionOptions(): Record<string, unknown> {\n return {\n host: this.options.connection.host,\n port: this.options.connection.port,\n password: this.options.connection.password,\n username: this.options.connection.username,\n db: this.options.connection.db,\n ...(this.options.connection.tls ? { tls: true } : {}),\n enableOfflineQueue: false,\n maxRetriesPerRequest: 1,\n };\n }\n\n private workerConnectionOptions(): Record<string, unknown> {\n return {\n host: this.options.connection.host,\n port: this.options.connection.port,\n password: this.options.connection.password,\n username: this.options.connection.username,\n db: this.options.connection.db,\n ...(this.options.connection.tls ? { tls: true } : {}),\n maxRetriesPerRequest: null,\n enableReadyCheck: false,\n };\n }\n\n // -------------------------------------------------------------------------\n // Close\n // -------------------------------------------------------------------------\n\n /**\n * Close the schedule queue. `removeJobScheduler` is called\n * first for every installed key so the next run of the host\n * app does not inherit leftover schedulers. Each removal is\n * best-effort: a failure on one key does not prevent the\n * others from being removed.\n */\n private async close(): Promise<void> {\n if (this.scheduleWorker !== null) {\n try {\n await this.scheduleWorker.close();\n } catch (err) {\n this.logger.warn(\n `Schedule worker close failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n this.scheduleWorker = null;\n }\n if (this.scheduleQueue !== null) {\n for (const key of this.installedKeys) {\n try {\n await this.scheduleQueue.removeJobScheduler(key);\n } catch (err) {\n this.logger.warn(\n `removeJobScheduler(\"${key}\") failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n try {\n await this.scheduleQueue.close();\n } catch (err) {\n this.logger.warn(\n `Schedule queue close failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n this.scheduleQueue = null;\n }\n }\n}\n"],"names":["BULLMQ_SCHEDULE_QUEUE_NAME","BullmqScheduleService","logger","Logger","name","scheduleQueue","scheduleWorker","installedKeys","Set","closePromise","scheduleRegistry","options","launcher","onApplicationBootstrap","buildScheduleQueue","autoStartWorker","buildScheduleWorker","entries","getAll","entry","installSchedule","err","warn","jobId","methodName","Error","message","String","log","size","length","onApplicationShutdown","close","installedSchedulerKeys","Array","from","inert","cron","timezone","schedulerKey","template","data","opts","attempts","backoff","type","delay","jitter","removeOnComplete","count","age","removeOnFail","upsertJobScheduler","pattern","tz","add","Queue","connection","producerConnectionOptions","defaultJobOptions","prefix","keyPrefix","skipWaitingForReady","skipVersionCheck","Worker","job","processScheduleFire","workerConnectionOptions","concurrency","scheduledAt","timestamp","Number","isFinite","Date","toISOString","params","scheduled","scheduleName","scheduleQueueJobId","id","execution","launch","status","host","port","password","username","db","tls","enableOfflineQueue","maxRetriesPerRequest","enableReadyCheck","key","removeJobScheduler"],"mappings":"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"}
@@ -24,6 +24,6 @@
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';
29
29
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC"}