@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/build/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { Q as QueueManagerConfig, W as WorkerCycle, A as Adapter, R as RetryConfig, b as JobFactory, c as Job, d as JobClass } from './index-C3_tlebh.js';
2
- export { e as customBackoff, f as exponentialBackoff, g as fixedBackoff, l as linearBackoff } from './index-C3_tlebh.js';
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-5PDDRF5O.js";
3
+ } from "./chunk-NPQKBCCY.js";
4
4
  import {
5
5
  Locator,
6
6
  QueueManager,
7
7
  debug_default
8
- } from "./chunk-CD45GT6E.js";
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-HMGNQSSG.js";
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 The unique job ID
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 id;
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 job ID
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
- /** Static options for this job class (queue, retries, timeout, etc.) */
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
- /** The payload data passed to this job instance */
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
- * Create a new job instance.
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 (provided by the worker)
416
+ * @param context - The job execution context
417
+ * @param signal - Optional abort signal for timeout handling
418
+ *
419
+ * @internal
207
420
  */
208
- constructor(payload, context) {
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 dispatcher = new JobDispatcher(
237
- this.jobName,
238
- payload
239
- );
240
- if (this.options.queue) {
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 (this.options.adapter) {
244
- dispatcher.with(this.options.adapter);
456
+ if (options.adapter) {
457
+ dispatcher.with(options.adapter);
245
458
  }
246
- if (this.options.priority !== void 0) {
247
- dispatcher.priority(this.options.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 = Object.freeze({
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, job.payload, context) : new JobClass(job.payload, context);
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(signal), abortPromise]);
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,