@boringnode/queue 0.0.1-alpha.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -32
- package/build/{chunk-5PDDRF5O.js → chunk-NPQKBCCY.js} +2 -2
- package/build/{chunk-CD45GT6E.js → chunk-RIXMQXYJ.js} +5 -4
- package/build/chunk-RIXMQXYJ.js.map +1 -0
- package/build/{chunk-HMGNQSSG.js → chunk-SMOKFZ46.js} +15 -1
- package/build/chunk-SMOKFZ46.js.map +1 -0
- package/build/{index-C3_tlebh.d.ts → index-C0Xg6F4E.d.ts} +433 -35
- package/build/index.d.ts +81 -4
- package/build/index.js +428 -30
- package/build/index.js.map +1 -1
- package/build/src/contracts/adapter.d.ts +1 -1
- package/build/src/drivers/knex_adapter.d.ts +8 -1
- package/build/src/drivers/knex_adapter.js +186 -34
- package/build/src/drivers/knex_adapter.js.map +1 -1
- package/build/src/drivers/redis_adapter.d.ts +7 -1
- package/build/src/drivers/redis_adapter.js +193 -2
- package/build/src/drivers/redis_adapter.js.map +1 -1
- package/build/src/drivers/sync_adapter.d.ts +7 -1
- package/build/src/drivers/sync_adapter.js +24 -5
- package/build/src/drivers/sync_adapter.js.map +1 -1
- package/build/src/types/index.d.ts +1 -1
- package/build/src/types/main.d.ts +1 -1
- package/package.json +3 -2
- package/build/chunk-CD45GT6E.js.map +0 -1
- package/build/chunk-HMGNQSSG.js.map +0 -1
- /package/build/{chunk-5PDDRF5O.js.map → chunk-NPQKBCCY.js.map} +0 -0
package/build/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Q as QueueManagerConfig, W as WorkerCycle, A as Adapter, R as RetryConfig,
|
|
2
|
-
export {
|
|
1
|
+
import { Q as QueueManagerConfig, W as WorkerCycle, A as Adapter, R as RetryConfig, d as JobFactory, e as Job, f as JobClass, b as ScheduleData, g as ScheduleStatus, c as ScheduleListOptions } from './index-C0Xg6F4E.js';
|
|
2
|
+
export { h as ScheduleBuilder, i as customBackoff, j as exponentialBackoff, k as fixedBackoff, l as linearBackoff } from './index-C0Xg6F4E.js';
|
|
3
3
|
import * as _poppinss_utils from '@poppinss/utils';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -345,6 +345,79 @@ declare class LocatorSingleton {
|
|
|
345
345
|
/** Global job class registry singleton */
|
|
346
346
|
declare const Locator: LocatorSingleton;
|
|
347
347
|
|
|
348
|
+
/**
|
|
349
|
+
* Represents a persisted job schedule.
|
|
350
|
+
*
|
|
351
|
+
* Use `Schedule.find()` or `Schedule.list()` to retrieve schedules,
|
|
352
|
+
* then use instance methods to manage them.
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* ```typescript
|
|
356
|
+
* const schedule = await Schedule.find('cleanup-daily')
|
|
357
|
+
* if (schedule) {
|
|
358
|
+
* await schedule.pause()
|
|
359
|
+
* // Later...
|
|
360
|
+
* await schedule.resume()
|
|
361
|
+
* }
|
|
362
|
+
*
|
|
363
|
+
* // List all active schedules
|
|
364
|
+
* const activeSchedules = await Schedule.list({ status: 'active' })
|
|
365
|
+
* ```
|
|
366
|
+
*/
|
|
367
|
+
declare class Schedule {
|
|
368
|
+
#private;
|
|
369
|
+
constructor(data: ScheduleData);
|
|
370
|
+
get id(): string;
|
|
371
|
+
get name(): string;
|
|
372
|
+
get payload(): any;
|
|
373
|
+
get cronExpression(): string | null;
|
|
374
|
+
get everyMs(): number | null;
|
|
375
|
+
get timezone(): string;
|
|
376
|
+
get from(): Date | null;
|
|
377
|
+
get to(): Date | null;
|
|
378
|
+
get limit(): number | null;
|
|
379
|
+
get runCount(): number;
|
|
380
|
+
get nextRunAt(): Date | null;
|
|
381
|
+
get lastRunAt(): Date | null;
|
|
382
|
+
get status(): ScheduleStatus;
|
|
383
|
+
get createdAt(): Date;
|
|
384
|
+
/**
|
|
385
|
+
* Find a schedule by ID.
|
|
386
|
+
*
|
|
387
|
+
* @param id - The schedule ID
|
|
388
|
+
* @returns The schedule instance, or null if not found
|
|
389
|
+
*/
|
|
390
|
+
static find(id: string): Promise<Schedule | null>;
|
|
391
|
+
/**
|
|
392
|
+
* List all schedules matching the given options.
|
|
393
|
+
*
|
|
394
|
+
* @param options - Optional filters for listing
|
|
395
|
+
* @returns Array of schedule instances
|
|
396
|
+
*/
|
|
397
|
+
static list(options?: ScheduleListOptions): Promise<Schedule[]>;
|
|
398
|
+
/**
|
|
399
|
+
* Pause this schedule.
|
|
400
|
+
* No jobs will be dispatched while paused.
|
|
401
|
+
*/
|
|
402
|
+
pause(): Promise<void>;
|
|
403
|
+
/**
|
|
404
|
+
* Resume this schedule.
|
|
405
|
+
* Jobs will be dispatched according to the schedule.
|
|
406
|
+
*/
|
|
407
|
+
resume(): Promise<void>;
|
|
408
|
+
/**
|
|
409
|
+
* Delete this schedule permanently.
|
|
410
|
+
*/
|
|
411
|
+
delete(): Promise<void>;
|
|
412
|
+
/**
|
|
413
|
+
* Trigger immediate execution of this schedule's job.
|
|
414
|
+
* Also updates runCount and lastRunAt.
|
|
415
|
+
*
|
|
416
|
+
* If the schedule has reached its limit, the job will not be dispatched.
|
|
417
|
+
*/
|
|
418
|
+
trigger(): Promise<void>;
|
|
419
|
+
}
|
|
420
|
+
|
|
348
421
|
declare const E_INVALID_DURATION_EXPRESSION: new (args?: any, options?: ErrorOptions) => _poppinss_utils.Exception;
|
|
349
422
|
declare const E_INVALID_BASE_DELAY: new (args: [reason: string], options?: ErrorOptions) => _poppinss_utils.Exception;
|
|
350
423
|
declare const E_INVALID_MAX_DELAY: new (args: [reason: string], options?: ErrorOptions) => _poppinss_utils.Exception;
|
|
@@ -356,20 +429,24 @@ declare const E_JOB_TIMEOUT: new (args: [jobName: string, timeout: number], opti
|
|
|
356
429
|
declare const E_QUEUE_NOT_INITIALIZED: new (args?: any, options?: ErrorOptions) => _poppinss_utils.Exception;
|
|
357
430
|
declare const E_ADAPTER_INIT_ERROR: new (args: [adapterName: string, originalMessage: string], options?: ErrorOptions) => _poppinss_utils.Exception;
|
|
358
431
|
declare const E_NO_JOBS_FOUND: new (args: [patterns: string], options?: ErrorOptions) => _poppinss_utils.Exception;
|
|
432
|
+
declare const E_INVALID_CRON_EXPRESSION: new (args: [expression: string, reason: string], options?: ErrorOptions) => _poppinss_utils.Exception;
|
|
433
|
+
declare const E_INVALID_SCHEDULE_CONFIG: new (args: [reason: string], options?: ErrorOptions) => _poppinss_utils.Exception;
|
|
359
434
|
|
|
360
435
|
declare const exceptions_E_ADAPTER_INIT_ERROR: typeof E_ADAPTER_INIT_ERROR;
|
|
361
436
|
declare const exceptions_E_CONFIGURATION_ERROR: typeof E_CONFIGURATION_ERROR;
|
|
362
437
|
declare const exceptions_E_INVALID_BASE_DELAY: typeof E_INVALID_BASE_DELAY;
|
|
438
|
+
declare const exceptions_E_INVALID_CRON_EXPRESSION: typeof E_INVALID_CRON_EXPRESSION;
|
|
363
439
|
declare const exceptions_E_INVALID_DURATION_EXPRESSION: typeof E_INVALID_DURATION_EXPRESSION;
|
|
364
440
|
declare const exceptions_E_INVALID_MAX_DELAY: typeof E_INVALID_MAX_DELAY;
|
|
365
441
|
declare const exceptions_E_INVALID_MULTIPLIER: typeof E_INVALID_MULTIPLIER;
|
|
442
|
+
declare const exceptions_E_INVALID_SCHEDULE_CONFIG: typeof E_INVALID_SCHEDULE_CONFIG;
|
|
366
443
|
declare const exceptions_E_JOB_MAX_ATTEMPTS_REACHED: typeof E_JOB_MAX_ATTEMPTS_REACHED;
|
|
367
444
|
declare const exceptions_E_JOB_NOT_FOUND: typeof E_JOB_NOT_FOUND;
|
|
368
445
|
declare const exceptions_E_JOB_TIMEOUT: typeof E_JOB_TIMEOUT;
|
|
369
446
|
declare const exceptions_E_NO_JOBS_FOUND: typeof E_NO_JOBS_FOUND;
|
|
370
447
|
declare const exceptions_E_QUEUE_NOT_INITIALIZED: typeof E_QUEUE_NOT_INITIALIZED;
|
|
371
448
|
declare namespace exceptions {
|
|
372
|
-
export { exceptions_E_ADAPTER_INIT_ERROR as E_ADAPTER_INIT_ERROR, exceptions_E_CONFIGURATION_ERROR as E_CONFIGURATION_ERROR, exceptions_E_INVALID_BASE_DELAY as E_INVALID_BASE_DELAY, exceptions_E_INVALID_DURATION_EXPRESSION as E_INVALID_DURATION_EXPRESSION, exceptions_E_INVALID_MAX_DELAY as E_INVALID_MAX_DELAY, exceptions_E_INVALID_MULTIPLIER as E_INVALID_MULTIPLIER, exceptions_E_JOB_MAX_ATTEMPTS_REACHED as E_JOB_MAX_ATTEMPTS_REACHED, exceptions_E_JOB_NOT_FOUND as E_JOB_NOT_FOUND, exceptions_E_JOB_TIMEOUT as E_JOB_TIMEOUT, exceptions_E_NO_JOBS_FOUND as E_NO_JOBS_FOUND, exceptions_E_QUEUE_NOT_INITIALIZED as E_QUEUE_NOT_INITIALIZED };
|
|
449
|
+
export { exceptions_E_ADAPTER_INIT_ERROR as E_ADAPTER_INIT_ERROR, exceptions_E_CONFIGURATION_ERROR as E_CONFIGURATION_ERROR, exceptions_E_INVALID_BASE_DELAY as E_INVALID_BASE_DELAY, exceptions_E_INVALID_CRON_EXPRESSION as E_INVALID_CRON_EXPRESSION, exceptions_E_INVALID_DURATION_EXPRESSION as E_INVALID_DURATION_EXPRESSION, exceptions_E_INVALID_MAX_DELAY as E_INVALID_MAX_DELAY, exceptions_E_INVALID_MULTIPLIER as E_INVALID_MULTIPLIER, exceptions_E_INVALID_SCHEDULE_CONFIG as E_INVALID_SCHEDULE_CONFIG, exceptions_E_JOB_MAX_ATTEMPTS_REACHED as E_JOB_MAX_ATTEMPTS_REACHED, exceptions_E_JOB_NOT_FOUND as E_JOB_NOT_FOUND, exceptions_E_JOB_TIMEOUT as E_JOB_TIMEOUT, exceptions_E_NO_JOBS_FOUND as E_NO_JOBS_FOUND, exceptions_E_QUEUE_NOT_INITIALIZED as E_QUEUE_NOT_INITIALIZED };
|
|
373
450
|
}
|
|
374
451
|
|
|
375
|
-
export { Job, Locator, QueueManager, Worker, exceptions as errors };
|
|
452
|
+
export { Job, Locator, QueueManager, Schedule, Worker, exceptions as errors };
|
package/build/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parse
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-NPQKBCCY.js";
|
|
4
4
|
import {
|
|
5
5
|
Locator,
|
|
6
6
|
QueueManager,
|
|
7
7
|
debug_default
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-RIXMQXYJ.js";
|
|
9
9
|
import {
|
|
10
10
|
DEFAULT_ERROR_RETRY_DELAY,
|
|
11
11
|
DEFAULT_IDLE_DELAY,
|
|
@@ -13,12 +13,14 @@ import {
|
|
|
13
13
|
DEFAULT_STALLED_INTERVAL,
|
|
14
14
|
DEFAULT_STALLED_THRESHOLD,
|
|
15
15
|
E_INVALID_BASE_DELAY,
|
|
16
|
+
E_INVALID_CRON_EXPRESSION,
|
|
16
17
|
E_INVALID_MAX_DELAY,
|
|
17
18
|
E_INVALID_MULTIPLIER,
|
|
19
|
+
E_INVALID_SCHEDULE_CONFIG,
|
|
18
20
|
E_JOB_MAX_ATTEMPTS_REACHED,
|
|
19
21
|
E_JOB_TIMEOUT,
|
|
20
22
|
exceptions_exports
|
|
21
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-SMOKFZ46.js";
|
|
22
24
|
|
|
23
25
|
// src/job_dispatcher.ts
|
|
24
26
|
import { randomUUID } from "crypto";
|
|
@@ -120,11 +122,11 @@ var JobDispatcher = class {
|
|
|
120
122
|
/**
|
|
121
123
|
* Dispatch the job to the queue.
|
|
122
124
|
*
|
|
123
|
-
* @returns
|
|
125
|
+
* @returns A DispatchResult containing the jobId
|
|
124
126
|
*
|
|
125
127
|
* @example
|
|
126
128
|
* ```typescript
|
|
127
|
-
* const jobId = await SendEmailJob.dispatch(payload).run()
|
|
129
|
+
* const { jobId } = await SendEmailJob.dispatch(payload).run()
|
|
128
130
|
* console.log(`Dispatched job: ${jobId}`)
|
|
129
131
|
* ```
|
|
130
132
|
*/
|
|
@@ -145,7 +147,9 @@ var JobDispatcher = class {
|
|
|
145
147
|
} else {
|
|
146
148
|
await adapter.pushOn(this.#queue, payload);
|
|
147
149
|
}
|
|
148
|
-
return
|
|
150
|
+
return {
|
|
151
|
+
jobId: id
|
|
152
|
+
};
|
|
149
153
|
}
|
|
150
154
|
/**
|
|
151
155
|
* Thenable implementation for auto-dispatch when awaited.
|
|
@@ -154,7 +158,7 @@ var JobDispatcher = class {
|
|
|
154
158
|
*
|
|
155
159
|
* @param onFulfilled - Success callback
|
|
156
160
|
* @param onRejected - Error callback
|
|
157
|
-
* @returns Promise resolving to the
|
|
161
|
+
* @returns Promise resolving to the DispatchResult
|
|
158
162
|
*/
|
|
159
163
|
then(onFulfilled, onRejected) {
|
|
160
164
|
return this.run().then(onFulfilled, onRejected);
|
|
@@ -170,13 +174,196 @@ var JobDispatcher = class {
|
|
|
170
174
|
}
|
|
171
175
|
};
|
|
172
176
|
|
|
177
|
+
// src/schedule_builder.ts
|
|
178
|
+
import { CronExpressionParser } from "cron-parser";
|
|
179
|
+
var ScheduleBuilder = class {
|
|
180
|
+
#name;
|
|
181
|
+
#payload;
|
|
182
|
+
#id;
|
|
183
|
+
#cronExpression;
|
|
184
|
+
#everyMs;
|
|
185
|
+
#timezone = "UTC";
|
|
186
|
+
#from;
|
|
187
|
+
#to;
|
|
188
|
+
#limit;
|
|
189
|
+
constructor(name, payload) {
|
|
190
|
+
this.#name = name;
|
|
191
|
+
this.#payload = payload;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Set a custom schedule ID.
|
|
195
|
+
* If not specified, defaults to the job name.
|
|
196
|
+
* If a schedule with this ID exists, it will be updated (upsert).
|
|
197
|
+
*/
|
|
198
|
+
id(scheduleId) {
|
|
199
|
+
this.#id = scheduleId;
|
|
200
|
+
return this;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Set a cron expression for the schedule.
|
|
204
|
+
* Mutually exclusive with `every()`.
|
|
205
|
+
*/
|
|
206
|
+
cron(expression) {
|
|
207
|
+
this.#cronExpression = expression;
|
|
208
|
+
return this;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Set a repeating interval for the schedule.
|
|
212
|
+
* Mutually exclusive with `cron()`.
|
|
213
|
+
*/
|
|
214
|
+
every(interval) {
|
|
215
|
+
this.#everyMs = parse(interval);
|
|
216
|
+
return this;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Set the timezone for cron evaluation.
|
|
220
|
+
* @default 'UTC'
|
|
221
|
+
*/
|
|
222
|
+
timezone(tz) {
|
|
223
|
+
this.#timezone = tz;
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Set the start boundary for the schedule.
|
|
228
|
+
* No jobs will be dispatched before this date.
|
|
229
|
+
*/
|
|
230
|
+
from(date) {
|
|
231
|
+
this.#from = date;
|
|
232
|
+
return this;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Set the end boundary for the schedule.
|
|
236
|
+
* No jobs will be dispatched after this date.
|
|
237
|
+
*/
|
|
238
|
+
to(date) {
|
|
239
|
+
this.#to = date;
|
|
240
|
+
return this;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Set both start and end boundaries for the schedule.
|
|
244
|
+
* Shorthand for `.from(start).to(end)`.
|
|
245
|
+
*/
|
|
246
|
+
between(from, to) {
|
|
247
|
+
return this.from(from).to(to);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Set the maximum number of runs for this schedule.
|
|
251
|
+
*/
|
|
252
|
+
limit(maxRuns) {
|
|
253
|
+
this.#limit = maxRuns;
|
|
254
|
+
return this;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Create the schedule and return the schedule ID.
|
|
258
|
+
*/
|
|
259
|
+
async run() {
|
|
260
|
+
if (!this.#cronExpression && !this.#everyMs) {
|
|
261
|
+
throw new E_INVALID_SCHEDULE_CONFIG([
|
|
262
|
+
"Schedule must have either a cron expression or an interval"
|
|
263
|
+
]);
|
|
264
|
+
}
|
|
265
|
+
if (this.#cronExpression && this.#everyMs) {
|
|
266
|
+
throw new E_INVALID_SCHEDULE_CONFIG([
|
|
267
|
+
"Schedule cannot have both a cron expression and an interval"
|
|
268
|
+
]);
|
|
269
|
+
}
|
|
270
|
+
if (this.#cronExpression) {
|
|
271
|
+
try {
|
|
272
|
+
CronExpressionParser.parse(this.#cronExpression, { tz: this.#timezone });
|
|
273
|
+
} catch (error) {
|
|
274
|
+
throw new E_INVALID_CRON_EXPRESSION([this.#cronExpression, error.message]);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const config = {
|
|
278
|
+
id: this.#id ?? this.#name,
|
|
279
|
+
name: this.#name,
|
|
280
|
+
payload: this.#payload,
|
|
281
|
+
cronExpression: this.#cronExpression,
|
|
282
|
+
everyMs: this.#everyMs,
|
|
283
|
+
timezone: this.#timezone,
|
|
284
|
+
from: this.#from,
|
|
285
|
+
to: this.#to,
|
|
286
|
+
limit: this.#limit
|
|
287
|
+
};
|
|
288
|
+
const adapter = QueueManager.use();
|
|
289
|
+
const scheduleId = await adapter.createSchedule(config);
|
|
290
|
+
const nextRunAt = this.#calculateNextRunAt();
|
|
291
|
+
await adapter.updateSchedule(scheduleId, { nextRunAt });
|
|
292
|
+
return { scheduleId };
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Calculate the next run time based on cron or interval.
|
|
296
|
+
*/
|
|
297
|
+
#calculateNextRunAt() {
|
|
298
|
+
const now = /* @__PURE__ */ new Date();
|
|
299
|
+
let nextRun;
|
|
300
|
+
if (this.#cronExpression) {
|
|
301
|
+
const cron = CronExpressionParser.parse(this.#cronExpression, {
|
|
302
|
+
currentDate: now,
|
|
303
|
+
tz: this.#timezone
|
|
304
|
+
});
|
|
305
|
+
nextRun = cron.next().toDate();
|
|
306
|
+
} else {
|
|
307
|
+
nextRun = new Date(now.getTime() + this.#everyMs);
|
|
308
|
+
}
|
|
309
|
+
if (this.#from && nextRun < this.#from) {
|
|
310
|
+
if (this.#cronExpression) {
|
|
311
|
+
const cron = CronExpressionParser.parse(this.#cronExpression, {
|
|
312
|
+
currentDate: this.#from,
|
|
313
|
+
tz: this.#timezone
|
|
314
|
+
});
|
|
315
|
+
nextRun = cron.next().toDate();
|
|
316
|
+
} else {
|
|
317
|
+
nextRun = this.#from;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return nextRun;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Implement PromiseLike to allow `await builder.every('5m')` syntax.
|
|
324
|
+
*/
|
|
325
|
+
then(onfulfilled, onrejected) {
|
|
326
|
+
return this.run().then(onfulfilled, onrejected);
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
173
330
|
// src/job.ts
|
|
174
331
|
var Job = class {
|
|
175
332
|
#payload;
|
|
176
333
|
#context;
|
|
177
|
-
|
|
334
|
+
#signal;
|
|
335
|
+
/**
|
|
336
|
+
* Static options for this job class.
|
|
337
|
+
*
|
|
338
|
+
* Override this property in subclasses to configure job behavior
|
|
339
|
+
* such as queue name, retry policy, timeout, and more.
|
|
340
|
+
*
|
|
341
|
+
* @example
|
|
342
|
+
* ```typescript
|
|
343
|
+
* class SendEmailJob extends Job<SendEmailPayload> {
|
|
344
|
+
* static options = {
|
|
345
|
+
* queue: 'emails',
|
|
346
|
+
* maxRetries: 3,
|
|
347
|
+
* timeout: '30s',
|
|
348
|
+
* }
|
|
349
|
+
* }
|
|
350
|
+
* ```
|
|
351
|
+
*/
|
|
178
352
|
static options = {};
|
|
179
|
-
/**
|
|
353
|
+
/**
|
|
354
|
+
* The payload data passed to this job instance.
|
|
355
|
+
*
|
|
356
|
+
* Contains the data provided when the job was dispatched.
|
|
357
|
+
* Available after the job has been hydrated by the worker.
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* ```typescript
|
|
361
|
+
* async execute() {
|
|
362
|
+
* const { to, subject, body } = this.payload
|
|
363
|
+
* await sendEmail(to, subject, body)
|
|
364
|
+
* }
|
|
365
|
+
* ```
|
|
366
|
+
*/
|
|
180
367
|
get payload() {
|
|
181
368
|
return this.#payload;
|
|
182
369
|
}
|
|
@@ -200,14 +387,41 @@ var Job = class {
|
|
|
200
387
|
return this.#context;
|
|
201
388
|
}
|
|
202
389
|
/**
|
|
203
|
-
*
|
|
390
|
+
* The abort signal for timeout handling.
|
|
391
|
+
*
|
|
392
|
+
* Check `signal.aborted` in long-running operations to handle timeouts gracefully.
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* ```typescript
|
|
396
|
+
* async execute() {
|
|
397
|
+
* for (const item of this.payload.items) {
|
|
398
|
+
* if (this.signal?.aborted) {
|
|
399
|
+
* throw new Error('Job timed out')
|
|
400
|
+
* }
|
|
401
|
+
* await processItem(item)
|
|
402
|
+
* }
|
|
403
|
+
* }
|
|
404
|
+
* ```
|
|
405
|
+
*/
|
|
406
|
+
get signal() {
|
|
407
|
+
return this.#signal;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Hydrate the job with payload, context, and optional abort signal.
|
|
411
|
+
*
|
|
412
|
+
* This method is called by the worker after instantiation to provide
|
|
413
|
+
* the job's runtime data. It should not be called directly by user code.
|
|
204
414
|
*
|
|
205
415
|
* @param payload - The data to be processed by this job
|
|
206
|
-
* @param context - The job execution context
|
|
416
|
+
* @param context - The job execution context
|
|
417
|
+
* @param signal - Optional abort signal for timeout handling
|
|
418
|
+
*
|
|
419
|
+
* @internal
|
|
207
420
|
*/
|
|
208
|
-
|
|
421
|
+
$hydrate(payload, context, signal) {
|
|
209
422
|
this.#payload = payload;
|
|
210
423
|
this.#context = Object.freeze(context);
|
|
424
|
+
this.#signal = signal;
|
|
211
425
|
}
|
|
212
426
|
/**
|
|
213
427
|
* Dispatch this job to the queue.
|
|
@@ -233,21 +447,50 @@ var Job = class {
|
|
|
233
447
|
* ```
|
|
234
448
|
*/
|
|
235
449
|
static dispatch(payload) {
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
)
|
|
240
|
-
|
|
241
|
-
dispatcher.toQueue(this.options.queue);
|
|
450
|
+
const options = this.options || {};
|
|
451
|
+
const jobName = options.name || this.name;
|
|
452
|
+
const dispatcher = new JobDispatcher(jobName, payload);
|
|
453
|
+
if (options.queue) {
|
|
454
|
+
dispatcher.toQueue(options.queue);
|
|
242
455
|
}
|
|
243
|
-
if (
|
|
244
|
-
dispatcher.with(
|
|
456
|
+
if (options.adapter) {
|
|
457
|
+
dispatcher.with(options.adapter);
|
|
245
458
|
}
|
|
246
|
-
if (
|
|
247
|
-
dispatcher.priority(
|
|
459
|
+
if (options.priority !== void 0) {
|
|
460
|
+
dispatcher.priority(options.priority);
|
|
248
461
|
}
|
|
249
462
|
return dispatcher;
|
|
250
463
|
}
|
|
464
|
+
/**
|
|
465
|
+
* Create a schedule for this job.
|
|
466
|
+
*
|
|
467
|
+
* Returns a ScheduleBuilder for fluent configuration before creating the schedule.
|
|
468
|
+
* The schedule is not actually created until `.run()` is called or the
|
|
469
|
+
* builder is awaited.
|
|
470
|
+
*
|
|
471
|
+
* @param payload - The data to pass to the job on each run
|
|
472
|
+
* @returns A ScheduleBuilder for fluent configuration
|
|
473
|
+
*
|
|
474
|
+
* @example
|
|
475
|
+
* ```typescript
|
|
476
|
+
* // Cron schedule
|
|
477
|
+
* await CleanupJob.schedule({ days: 30 })
|
|
478
|
+
* .id('cleanup-daily')
|
|
479
|
+
* .cron('0 0 * * *')
|
|
480
|
+
* .timezone('Europe/Paris')
|
|
481
|
+
* .run()
|
|
482
|
+
*
|
|
483
|
+
* // Interval schedule
|
|
484
|
+
* await SyncJob.schedule({ source: 'api' })
|
|
485
|
+
* .every('5m')
|
|
486
|
+
* .run()
|
|
487
|
+
* ```
|
|
488
|
+
*/
|
|
489
|
+
static schedule(payload) {
|
|
490
|
+
const options = this.options || {};
|
|
491
|
+
const jobName = options.name || this.name;
|
|
492
|
+
return new ScheduleBuilder(jobName, payload);
|
|
493
|
+
}
|
|
251
494
|
};
|
|
252
495
|
|
|
253
496
|
// src/worker.ts
|
|
@@ -510,6 +753,7 @@ var Worker = class {
|
|
|
510
753
|
while (this.#running) {
|
|
511
754
|
try {
|
|
512
755
|
await this.#checkStalledJobs(queues);
|
|
756
|
+
await this.#dispatchDueSchedules();
|
|
513
757
|
yield* this.#fillPool(queues);
|
|
514
758
|
if (this.#pool.isEmpty()) {
|
|
515
759
|
yield { type: "idle", suggestedDelay: this.#idleDelay };
|
|
@@ -542,9 +786,9 @@ var Worker = class {
|
|
|
542
786
|
async #execute(job, queue) {
|
|
543
787
|
const startTime = performance.now();
|
|
544
788
|
debug_default("worker %s: executing job %s (%s)", this.#id, job.id, job.name);
|
|
545
|
-
const { instance, options, timeout } = await this.#initJob(job, queue);
|
|
789
|
+
const { instance, options, timeout, context, payload } = await this.#initJob(job, queue);
|
|
546
790
|
try {
|
|
547
|
-
await this.#executeWithTimeout(instance, timeout);
|
|
791
|
+
await this.#executeWithTimeout(instance, payload, context, timeout);
|
|
548
792
|
await this.#adapter.completeJob(job.id, queue);
|
|
549
793
|
const duration = (performance.now() - startTime).toFixed(2);
|
|
550
794
|
debug_default("worker %s: successfully executed job %s in %dms", this.#id, job.id, duration);
|
|
@@ -588,7 +832,7 @@ var Worker = class {
|
|
|
588
832
|
async #initJob(job, queue) {
|
|
589
833
|
try {
|
|
590
834
|
const JobClass = Locator.getOrThrow(job.name);
|
|
591
|
-
const context =
|
|
835
|
+
const context = {
|
|
592
836
|
jobId: job.id,
|
|
593
837
|
name: job.name,
|
|
594
838
|
attempt: job.attempts + 1,
|
|
@@ -596,12 +840,12 @@ var Worker = class {
|
|
|
596
840
|
priority: job.priority ?? DEFAULT_PRIORITY,
|
|
597
841
|
acquiredAt: new Date(job.acquiredAt),
|
|
598
842
|
stalledCount: job.stalledCount ?? 0
|
|
599
|
-
}
|
|
843
|
+
};
|
|
600
844
|
const jobFactory = QueueManager.getJobFactory();
|
|
601
|
-
const instance = jobFactory ? await jobFactory(JobClass
|
|
845
|
+
const instance = jobFactory ? await jobFactory(JobClass) : new JobClass();
|
|
602
846
|
const options = JobClass.options || {};
|
|
603
847
|
const timeout = this.#getJobTimeout(options);
|
|
604
|
-
return { instance, options, timeout };
|
|
848
|
+
return { instance, options, timeout, context, payload: job.payload };
|
|
605
849
|
} catch (error) {
|
|
606
850
|
debug_default("worker %s: failed to initialize job %s (%s)", this.#id, job.id, job.name);
|
|
607
851
|
await this.#adapter.failJob(job.id, queue, error);
|
|
@@ -617,17 +861,19 @@ var Worker = class {
|
|
|
617
861
|
}
|
|
618
862
|
return void 0;
|
|
619
863
|
}
|
|
620
|
-
async #executeWithTimeout(instance, timeout) {
|
|
864
|
+
async #executeWithTimeout(instance, payload, context, timeout) {
|
|
621
865
|
if (!timeout) {
|
|
866
|
+
instance.$hydrate(payload, context);
|
|
622
867
|
return instance.execute();
|
|
623
868
|
}
|
|
624
869
|
const signal = AbortSignal.timeout(timeout);
|
|
870
|
+
instance.$hydrate(payload, context, signal);
|
|
625
871
|
const abortPromise = new Promise((_, reject) => {
|
|
626
872
|
signal.addEventListener("abort", () => {
|
|
627
873
|
reject(new E_JOB_TIMEOUT([instance.constructor.name, timeout]));
|
|
628
874
|
});
|
|
629
875
|
});
|
|
630
|
-
await Promise.race([instance.execute(
|
|
876
|
+
await Promise.race([instance.execute(), abortPromise]);
|
|
631
877
|
}
|
|
632
878
|
async #acquireNextJob(queues) {
|
|
633
879
|
for (const queue of queues) {
|
|
@@ -678,6 +924,156 @@ var Worker = class {
|
|
|
678
924
|
this.#shutdownHandler = void 0;
|
|
679
925
|
}
|
|
680
926
|
}
|
|
927
|
+
/**
|
|
928
|
+
* Dispatch any due scheduled jobs.
|
|
929
|
+
*
|
|
930
|
+
* Claims due schedules from the adapter and dispatches the corresponding
|
|
931
|
+
* jobs to their configured queues.
|
|
932
|
+
*/
|
|
933
|
+
async #dispatchDueSchedules() {
|
|
934
|
+
while (true) {
|
|
935
|
+
const schedule = await this.#adapter.claimDueSchedule();
|
|
936
|
+
if (!schedule) {
|
|
937
|
+
break;
|
|
938
|
+
}
|
|
939
|
+
debug_default(
|
|
940
|
+
"worker %s: dispatching scheduled job %s (schedule: %s, runCount: %d)",
|
|
941
|
+
this.#id,
|
|
942
|
+
schedule.name,
|
|
943
|
+
schedule.id,
|
|
944
|
+
schedule.runCount + 1
|
|
945
|
+
);
|
|
946
|
+
const JobClass = Locator.get(schedule.name);
|
|
947
|
+
const queue = JobClass?.options?.queue ?? "default";
|
|
948
|
+
await this.#adapter.pushOn(queue, {
|
|
949
|
+
id: randomUUID2(),
|
|
950
|
+
name: schedule.name,
|
|
951
|
+
payload: schedule.payload,
|
|
952
|
+
attempts: 0,
|
|
953
|
+
priority: JobClass?.options?.priority
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
|
|
959
|
+
// src/schedule.ts
|
|
960
|
+
var Schedule = class _Schedule {
|
|
961
|
+
#data;
|
|
962
|
+
constructor(data) {
|
|
963
|
+
this.#data = data;
|
|
964
|
+
}
|
|
965
|
+
get id() {
|
|
966
|
+
return this.#data.id;
|
|
967
|
+
}
|
|
968
|
+
get name() {
|
|
969
|
+
return this.#data.name;
|
|
970
|
+
}
|
|
971
|
+
get payload() {
|
|
972
|
+
return this.#data.payload;
|
|
973
|
+
}
|
|
974
|
+
get cronExpression() {
|
|
975
|
+
return this.#data.cronExpression;
|
|
976
|
+
}
|
|
977
|
+
get everyMs() {
|
|
978
|
+
return this.#data.everyMs;
|
|
979
|
+
}
|
|
980
|
+
get timezone() {
|
|
981
|
+
return this.#data.timezone;
|
|
982
|
+
}
|
|
983
|
+
get from() {
|
|
984
|
+
return this.#data.from;
|
|
985
|
+
}
|
|
986
|
+
get to() {
|
|
987
|
+
return this.#data.to;
|
|
988
|
+
}
|
|
989
|
+
get limit() {
|
|
990
|
+
return this.#data.limit;
|
|
991
|
+
}
|
|
992
|
+
get runCount() {
|
|
993
|
+
return this.#data.runCount;
|
|
994
|
+
}
|
|
995
|
+
get nextRunAt() {
|
|
996
|
+
return this.#data.nextRunAt;
|
|
997
|
+
}
|
|
998
|
+
get lastRunAt() {
|
|
999
|
+
return this.#data.lastRunAt;
|
|
1000
|
+
}
|
|
1001
|
+
get status() {
|
|
1002
|
+
return this.#data.status;
|
|
1003
|
+
}
|
|
1004
|
+
get createdAt() {
|
|
1005
|
+
return this.#data.createdAt;
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Find a schedule by ID.
|
|
1009
|
+
*
|
|
1010
|
+
* @param id - The schedule ID
|
|
1011
|
+
* @returns The schedule instance, or null if not found
|
|
1012
|
+
*/
|
|
1013
|
+
static async find(id) {
|
|
1014
|
+
const adapter = QueueManager.use();
|
|
1015
|
+
const data = await adapter.getSchedule(id);
|
|
1016
|
+
if (!data) return null;
|
|
1017
|
+
return new _Schedule(data);
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* List all schedules matching the given options.
|
|
1021
|
+
*
|
|
1022
|
+
* @param options - Optional filters for listing
|
|
1023
|
+
* @returns Array of schedule instances
|
|
1024
|
+
*/
|
|
1025
|
+
static async list(options) {
|
|
1026
|
+
const adapter = QueueManager.use();
|
|
1027
|
+
const schedules = await adapter.listSchedules(options);
|
|
1028
|
+
return schedules.map((data) => new _Schedule(data));
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Pause this schedule.
|
|
1032
|
+
* No jobs will be dispatched while paused.
|
|
1033
|
+
*/
|
|
1034
|
+
async pause() {
|
|
1035
|
+
const adapter = QueueManager.use();
|
|
1036
|
+
await adapter.updateSchedule(this.#data.id, { status: "paused" });
|
|
1037
|
+
this.#data.status = "paused";
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Resume this schedule.
|
|
1041
|
+
* Jobs will be dispatched according to the schedule.
|
|
1042
|
+
*/
|
|
1043
|
+
async resume() {
|
|
1044
|
+
const adapter = QueueManager.use();
|
|
1045
|
+
await adapter.updateSchedule(this.#data.id, { status: "active" });
|
|
1046
|
+
this.#data.status = "active";
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Delete this schedule permanently.
|
|
1050
|
+
*/
|
|
1051
|
+
async delete() {
|
|
1052
|
+
const adapter = QueueManager.use();
|
|
1053
|
+
await adapter.deleteSchedule(this.#data.id);
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Trigger immediate execution of this schedule's job.
|
|
1057
|
+
* Also updates runCount and lastRunAt.
|
|
1058
|
+
*
|
|
1059
|
+
* If the schedule has reached its limit, the job will not be dispatched.
|
|
1060
|
+
*/
|
|
1061
|
+
async trigger() {
|
|
1062
|
+
if (this.#data.limit !== null && this.#data.runCount >= this.#data.limit) {
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
const adapter = QueueManager.use();
|
|
1066
|
+
const dispatcher = new JobDispatcher(this.#data.name, this.#data.payload);
|
|
1067
|
+
await dispatcher.run();
|
|
1068
|
+
const now = /* @__PURE__ */ new Date();
|
|
1069
|
+
const newRunCount = this.#data.runCount + 1;
|
|
1070
|
+
await adapter.updateSchedule(this.#data.id, {
|
|
1071
|
+
runCount: newRunCount,
|
|
1072
|
+
lastRunAt: now
|
|
1073
|
+
});
|
|
1074
|
+
this.#data.runCount = newRunCount;
|
|
1075
|
+
this.#data.lastRunAt = now;
|
|
1076
|
+
}
|
|
681
1077
|
};
|
|
682
1078
|
|
|
683
1079
|
// src/strategies/backoff_strategy.ts
|
|
@@ -829,6 +1225,8 @@ export {
|
|
|
829
1225
|
Job,
|
|
830
1226
|
Locator,
|
|
831
1227
|
QueueManager,
|
|
1228
|
+
Schedule,
|
|
1229
|
+
ScheduleBuilder,
|
|
832
1230
|
Worker,
|
|
833
1231
|
customBackoff,
|
|
834
1232
|
exceptions_exports as errors,
|