@@ -29,7 +29,7 @@ npm install @boringnode/queue
2929- ** Priority Queues** : Process high-priority jobs first
3030- ** Retry with Backoff** : Automatic retries with exponential, linear, or fixed backoff strategies
3131- ** Job Timeout** : Automatically fail or retry jobs that exceed a time limit
32- - ** Repeating Jobs** : Schedule jobs to repeat at fixed intervals
32+ - ** Scheduled Jobs** : Cron-based or interval-based job scheduling with pause/resume support
3333
3434## Quick Start
3535
@@ -355,18 +355,15 @@ export default class MyJob extends Job<Payload> {
355355
356356### Context Properties
357357
358- | Property | Type | Description |
359- | -------------------| ---------------------| ---------------------------------------------------|
360- | ` jobId ` | string | Unique identifier for this job |
361- | ` name ` | string | Job class name |
362- | ` attempt ` | number | Current attempt number (1-based) |
363- | ` queue ` | string | Queue name this job is being processed from |
364- | ` priority ` | number | Job priority (lower = higher priority) |
365- | ` acquiredAt ` | Date | When this job was acquired by the worker |
366- | ` stalledCount ` | number | Times this job was recovered from stalled state |
367- | ` isRepeating ` | boolean | Whether this job is configured to repeat |
368- | ` repeatRemaining ` | number \| undefined | Remaining repetitions (undefined = infinite) |
369- | ` repeatId ` | string \| undefined | Unique ID for the repeat chain (for cancellation) |
358+ | Property | Type | Description |
359+ | ----------------| --------| -------------------------------------------------|
360+ | ` jobId ` | string | Unique identifier for this job |
361+ | ` name ` | string | Job class name |
362+ | ` attempt ` | number | Current attempt number (1-based) |
363+ | ` queue ` | string | Queue name this job is being processed from |
364+ | ` priority ` | number | Job priority (lower = higher priority) |
365+ | ` acquiredAt ` | Date | When this job was acquired by the worker |
366+ | ` stalledCount ` | number | Times this job was recovered from stalled state |
370367
371368## Dependency Injection
372369
@@ -417,87 +414,96 @@ export default class SendEmailJob extends Job<SendEmailPayload> {
417414
418415Without a ` jobFactory ` , jobs are instantiated with ` new JobClass(payload, context) ` .
419416
420- ## Repeating Jobs
417+ ## Scheduled Jobs
421418
422- Schedule jobs to repeat automatically at fixed intervals:
419+ Schedule jobs to run on a recurring basis using cron expressions or fixed intervals. Schedules are persisted and survive worker restarts.
423420
424- ``` typescript
425- // Repeat every 5 seconds indefinitely
426- await SyncJob .dispatch ({ source: ' api' }).every (' 5s' )
427-
428- // Repeat every hour, 10 times total
429- await CleanupJob .dispatch ({ days: 30 }).every (' 1h' ).times (10 )
421+ ### Creating a Schedule
430422
431- // Combine with delay (start after 30 seconds, then repeat every minute)
432- await ReportJob .dispatch ({ type: ' daily' }).in (' 30s' ).every (' 1m' )
423+ ``` typescript
424+ import { Schedule } from ' @boringnode/queue'
425+
426+ // Run every 10 seconds (uses job name as schedule ID by default)
427+ const { scheduleId } = await MetricsJob .schedule ({ endpoint: ' /api/health' }).every (' 10s' ).run ()
428+
429+ // Run on a cron schedule with custom ID
430+ await CleanupJob .schedule ({ days: 30 })
431+ .id (' daily-cleanup' ) // Custom ID (optional, defaults to job name)
432+ .cron (' 0 * * * *' ) // Every hour at minute 0
433+ .timezone (' Europe/Paris' ) // Optional timezone (default: UTC)
434+ .run ()
435+
436+ // Schedule with constraints
437+ await ReportJob .schedule ({ type: ' weekly' })
438+ .id (' weekly-report' )
439+ .cron (' 0 9 * * MON' ) // Every Monday at 9am
440+ .from (new Date (' 2024-01-01' )) // Start date
441+ .to (new Date (' 2024-12-31' )) // End date
442+ .limit (52 ) // Maximum 52 runs
443+ .run ()
433444```
434445
435- ### Cancelling a Repeating Job
436-
437- When dispatching a repeating job, you receive a ` repeatId ` that can be used to cancel the entire repeat chain from anywhere:
446+ ### Managing Schedules
438447
439448``` typescript
440- import { QueueManager } from ' @boringnode/queue'
449+ import { Schedule } from ' @boringnode/queue'
441450
442- // Dispatch returns jobId and repeatId
443- const { jobId, repeatId } = await SyncJob . dispatch ({ source: ' api ' }). every ( ' 5s ' )
451+ // Find a schedule by ID
452+ const schedule = await Schedule . find ( ' health-check ' )
444453
445- console .log (` Started repeating job ${jobId } with repeat chain ${repeatId } ` )
454+ if (schedule ) {
455+ console .log (` Status: ${schedule .status } ` ) // 'active' or 'paused'
456+ console .log (` Run count: ${schedule .runCount } ` )
457+ console .log (` Next run: ${schedule .nextRunAt } ` )
458+ console .log (` Last run: ${schedule .lastRunAt } ` )
446459
447- // Later, cancel the repeat chain from anywhere
448- if (repeatId ) {
449- await QueueManager .cancelRepeat (repeatId )
450- }
451- ```
452-
453- The ` repeatId ` is also available inside the job via ` this.context.repeatId ` .
454-
455- ### Stopping from Within the Job
460+ // Pause the schedule
461+ await schedule .pause ()
456462
457- A job can stop its own repetition by calling ` this.stopRepeating() ` :
463+ // Resume the schedule
464+ await schedule .resume ()
458465
459- ``` typescript
460- import { Job } from ' @boringnode/queue'
461- import type { JobContext } from ' @boringnode/queue/types'
466+ // Trigger an immediate run (outside of the normal schedule)
467+ await schedule .trigger ()
462468
463- export default class SyncJob extends Job <SyncPayload > {
464- static readonly jobName = ' SyncJob'
465-
466- async execute(): Promise <void > {
467- const result = await this .syncData ()
468-
469- // Stop repeating when sync is complete
470- if (result .isComplete ) {
471- this .stopRepeating ()
472- }
473- }
469+ // Delete the schedule
470+ await schedule .delete ()
474471}
475472```
476473
477- ### Repeat Context
478-
479- Jobs have access to repeat information via ` this.context ` :
474+ ### Listing Schedules
480475
481476``` typescript
482- async execute (): Promise < void > {
483- if (this.context.isRepeating) {
484- console .log (` Repeating job, ${this .context .repeatRemaining ?? ' infinite' } runs remaining ` )
485- }
486- }
487- ```
477+ import { Schedule } from ' @boringnode/queue'
488478
489- | Property | Type | Description |
490- | -------------------| ---------------------| ---------------------------------------------------|
491- | ` isRepeating ` | boolean | Whether this job is configured to repeat |
492- | ` repeatRemaining ` | number \| undefined | Remaining repetitions (undefined = infinite) |
493- | ` repeatId ` | string \| undefined | Unique ID for the repeat chain (for cancellation) |
479+ // List all schedules
480+ const all = await Schedule .list ()
494481
495- ### How Repeating Works
482+ // Filter by status
483+ const active = await Schedule .list ({ status: ' active' })
484+ const paused = await Schedule .list ({ status: ' paused' })
485+ ```
496486
497- - Each repeat creates a ** new job** with a new ID
498- - The payload is ** preserved** across repeats
499- - Failed jobs do ** not** repeat (only successful completions trigger the next run)
500- - The repeat interval is the delay ** between** job completions
487+ ### Schedule Options
488+
489+ | Method | Description |
490+ | ----------------------| -------------------------------------------------|
491+ | ` .id(string) ` | Unique identifier (defaults to job name) |
492+ | ` .every(duration) ` | Run at fixed intervals ('5s', '1m', '1h', '1d') |
493+ | ` .cron(expression) ` | Run on a cron schedule |
494+ | ` .timezone(tz) ` | Timezone for cron expressions (default: 'UTC') |
495+ | ` .from(date) ` | Don't run before this date |
496+ | ` .to(date) ` | Don't run after this date |
497+ | ` .between(from, to) ` | Shorthand for ` .from().to() ` |
498+ | ` .limit(n) ` | Maximum number of runs |
499+
500+ ### How Scheduling Works
501+
502+ - Schedules are ** persisted** in the database (via the adapter)
503+ - The ** Worker** polls for due schedules and dispatches jobs automatically
504+ - Each schedule run creates a ** new job** with a unique ID
505+ - Multiple workers can run concurrently - only one will claim each due schedule
506+ - Failed jobs do ** not** affect the schedule (the next run will still occur)
501507
502508## Job Discovery
503509
0 commit comments