@nest-batch/bullmq 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/dist/src/adapters/bullmq.adapter.d.ts +2 -2
- package/dist/src/adapters/bullmq.adapter.d.ts.map +1 -1
- package/dist/src/adapters/bullmq.adapter.js +9 -9
- package/dist/src/adapters/bullmq.adapter.js.map +1 -1
- package/dist/src/bullmq-execution-strategy.d.ts +3 -3
- package/dist/src/bullmq-execution-strategy.d.ts.map +1 -1
- package/dist/src/bullmq-execution-strategy.js +4 -4
- package/dist/src/bullmq-execution-strategy.js.map +1 -1
- package/dist/src/bullmq-runtime.d.ts +237 -0
- package/dist/src/bullmq-runtime.d.ts.map +1 -0
- package/dist/src/bullmq-runtime.js +441 -0
- package/dist/src/bullmq-runtime.js.map +1 -0
- package/dist/src/bullmq-schedule.d.ts +134 -0
- package/dist/src/bullmq-schedule.d.ts.map +1 -0
- package/dist/src/bullmq-schedule.js +290 -0
- package/dist/src/bullmq-schedule.js.map +1 -0
- package/dist/src/bullmq-schedule.service.d.ts +21 -9
- package/dist/src/bullmq-schedule.service.d.ts.map +1 -1
- package/dist/src/bullmq-schedule.service.js +57 -3
- package/dist/src/bullmq-schedule.service.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/package.json +3 -3
- package/src/adapters/bullmq.adapter.ts +17 -21
- package/src/bullmq-execution-strategy.ts +3 -6
- package/src/{bullmq-runtime.service.ts → bullmq-runtime.ts} +10 -17
- package/src/{bullmq-schedule.service.ts → bullmq-schedule.ts} +110 -22
- package/src/index.ts +1 -1
|
@@ -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":"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"}
|
|
@@ -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).
|
|
32
|
-
*
|
|
33
|
-
*
|
|
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
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
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,
|
|
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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"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -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
|
|
27
|
+
export * from './bullmq-schedule';
|
|
28
28
|
export * from './adapters';
|
|
29
29
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|