@boringnode/queue 0.0.1-alpha.4 → 0.1.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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # @boringnode/queue
2
2
 
3
+ <div align="center">
4
+
5
+ [![typescript-image]][typescript-url]
6
+ [![gh-workflow-image]][gh-workflow-url]
7
+ [![npm-image]][npm-url]
8
+ [![npm-download-image]][npm-download-url]
9
+ [![license-image]][license-url]
10
+
11
+ </div>
12
+
3
13
  A simple and efficient queue system for Node.js applications. Built for simplicity and ease of use, `@boringnode/queue` allows you to dispatch background jobs and process them asynchronously with support for multiple queue adapters.
4
14
 
5
15
  ## Installation
@@ -19,6 +29,7 @@ npm install @boringnode/queue
19
29
  - **Priority Queues**: Process high-priority jobs first
20
30
  - **Retry with Backoff**: Automatic retries with exponential, linear, or fixed backoff strategies
21
31
  - **Job Timeout**: Automatically fail or retry jobs that exceed a time limit
32
+ - **Scheduled Jobs**: Cron-based or interval-based job scheduling with pause/resume support
22
33
 
23
34
  ## Quick Start
24
35
 
@@ -41,10 +52,6 @@ export default class SendEmailJob extends Job<SendEmailPayload> {
41
52
  queue: 'email',
42
53
  }
43
54
 
44
- constructor(payload: SendEmailPayload, context: JobContext) {
45
- super(payload, context)
46
- }
47
-
48
55
  async execute(): Promise<void> {
49
56
  console.log(`[Attempt ${this.context.attempt}] Sending email to: ${this.payload.to}`)
50
57
  }
@@ -125,7 +132,7 @@ interface QueueManagerConfig {
125
132
  // Worker configuration
126
133
  worker: {
127
134
  concurrency: number
128
- pollingInterval: string
135
+ idleDelay: Duration
129
136
  }
130
137
 
131
138
  // Job discovery locations
@@ -232,9 +239,9 @@ Schedule jobs to run in the future:
232
239
  ```typescript
233
240
  // Various time formats
234
241
  await SendEmailJob.dispatch(payload).in('30s') // 30 seconds
235
- await SendEmailJob.dispatch(payload).in('5m') // 5 minutes
236
- await SendEmailJob.dispatch(payload).in('2h') // 2 hours
237
- await SendEmailJob.dispatch(payload).in('1d') // 1 day
242
+ await SendEmailJob.dispatch(payload).in('5m') // 5 minutes
243
+ await SendEmailJob.dispatch(payload).in('2h') // 2 hours
244
+ await SendEmailJob.dispatch(payload).in('1d') // 1 day
238
245
  ```
239
246
 
240
247
  ## Priority
@@ -349,7 +356,7 @@ export default class MyJob extends Job<Payload> {
349
356
  ### Context Properties
350
357
 
351
358
  | Property | Type | Description |
352
- | -------------- | ------ | ----------------------------------------------- |
359
+ |----------------|--------|-------------------------------------------------|
353
360
  | `jobId` | string | Unique identifier for this job |
354
361
  | `name` | string | Job class name |
355
362
  | `attempt` | number | Current attempt number (1-based) |
@@ -407,6 +414,97 @@ export default class SendEmailJob extends Job<SendEmailPayload> {
407
414
 
408
415
  Without a `jobFactory`, jobs are instantiated with `new JobClass(payload, context)`.
409
416
 
417
+ ## Scheduled Jobs
418
+
419
+ Schedule jobs to run on a recurring basis using cron expressions or fixed intervals. Schedules are persisted and survive worker restarts.
420
+
421
+ ### Creating a Schedule
422
+
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()
444
+ ```
445
+
446
+ ### Managing Schedules
447
+
448
+ ```typescript
449
+ import { Schedule } from '@boringnode/queue'
450
+
451
+ // Find a schedule by ID
452
+ const schedule = await Schedule.find('health-check')
453
+
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}`)
459
+
460
+ // Pause the schedule
461
+ await schedule.pause()
462
+
463
+ // Resume the schedule
464
+ await schedule.resume()
465
+
466
+ // Trigger an immediate run (outside of the normal schedule)
467
+ await schedule.trigger()
468
+
469
+ // Delete the schedule
470
+ await schedule.delete()
471
+ }
472
+ ```
473
+
474
+ ### Listing Schedules
475
+
476
+ ```typescript
477
+ import { Schedule } from '@boringnode/queue'
478
+
479
+ // List all schedules
480
+ const all = await Schedule.list()
481
+
482
+ // Filter by status
483
+ const active = await Schedule.list({ status: 'active' })
484
+ const paused = await Schedule.list({ status: 'paused' })
485
+ ```
486
+
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)
507
+
410
508
  ## Job Discovery
411
509
 
412
510
  The queue manager automatically discovers and registers jobs from the specified locations:
@@ -449,7 +547,7 @@ By default, a simple console logger is used that only outputs warnings and error
449
547
  Performance comparison with BullMQ using realistic jobs (5ms simulated work per job):
450
548
 
451
549
  | Jobs | Concurrency | @boringnode/queue | BullMQ | Diff |
452
- | ---- | ----------- | ----------------- | ------ | ----------- |
550
+ |------|-------------|-------------------|--------|-------------|
453
551
  | 100 | 1 | 562ms | 596ms | 5.7% faster |
454
552
  | 100 | 5 | 116ms | 117ms | ~same |
455
553
  | 100 | 10 | 62ms | 62ms | ~same |
@@ -475,3 +573,14 @@ npm run benchmark
475
573
  # Custom job duration
476
574
  npm run benchmark -- --duration=10
477
575
  ```
576
+
577
+ [gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/boringnode/queue/checks.yml?branch=main&style=for-the-badge
578
+ [gh-workflow-url]: https://github.com/boringnode/queue/actions/workflows/checks.yml
579
+ [npm-image]: https://img.shields.io/npm/v/@boringnode/queue.svg?style=for-the-badge&logo=npm
580
+ [npm-url]: https://www.npmjs.com/package/@boringnode/queue
581
+ [npm-download-image]: https://img.shields.io/npm/dm/@boringnode/queue?style=for-the-badge
582
+ [npm-download-url]: https://www.npmjs.com/package/@boringnode/queue
583
+ [typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
584
+ [typescript-url]: https://www.typescriptlang.org
585
+ [license-image]: https://img.shields.io/npm/l/@boringnode/queue?color=blueviolet&style=for-the-badge
586
+ [license-url]: LICENSE.md
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  E_INVALID_DURATION_EXPRESSION,
3
3
  PRIORITY_SCORE_MULTIPLIER
4
- } from "./chunk-HMGNQSSG.js";
4
+ } from "./chunk-SMOKFZ46.js";
5
5
 
6
6
  // src/utils.ts
7
7
  import { parse as parseDuration } from "@lukeed/ms";
@@ -23,4 +23,4 @@ export {
23
23
  parse,
24
24
  calculateScore
25
25
  };
26
- //# sourceMappingURL=chunk-5PDDRF5O.js.map
26
+ //# sourceMappingURL=chunk-NPQKBCCY.js.map
@@ -10,9 +10,11 @@ __export(exceptions_exports, {
10
10
  E_ADAPTER_INIT_ERROR: () => E_ADAPTER_INIT_ERROR,
11
11
  E_CONFIGURATION_ERROR: () => E_CONFIGURATION_ERROR,
12
12
  E_INVALID_BASE_DELAY: () => E_INVALID_BASE_DELAY,
13
+ E_INVALID_CRON_EXPRESSION: () => E_INVALID_CRON_EXPRESSION,
13
14
  E_INVALID_DURATION_EXPRESSION: () => E_INVALID_DURATION_EXPRESSION,
14
15
  E_INVALID_MAX_DELAY: () => E_INVALID_MAX_DELAY,
15
16
  E_INVALID_MULTIPLIER: () => E_INVALID_MULTIPLIER,
17
+ E_INVALID_SCHEDULE_CONFIG: () => E_INVALID_SCHEDULE_CONFIG,
16
18
  E_JOB_MAX_ATTEMPTS_REACHED: () => E_JOB_MAX_ATTEMPTS_REACHED,
17
19
  E_JOB_NOT_FOUND: () => E_JOB_NOT_FOUND,
18
20
  E_JOB_TIMEOUT: () => E_JOB_TIMEOUT,
@@ -72,6 +74,16 @@ var E_NO_JOBS_FOUND = createError(
72
74
  "E_NO_JOBS_FOUND",
73
75
  500
74
76
  );
77
+ var E_INVALID_CRON_EXPRESSION = createError(
78
+ 'Invalid cron expression "%s": %s',
79
+ "E_INVALID_CRON_EXPRESSION",
80
+ 500
81
+ );
82
+ var E_INVALID_SCHEDULE_CONFIG = createError(
83
+ "Invalid schedule configuration: %s",
84
+ "E_INVALID_SCHEDULE_CONFIG",
85
+ 500
86
+ );
75
87
 
76
88
  // src/constants.ts
77
89
  var DEFAULT_PRIORITY = 5;
@@ -92,6 +104,8 @@ export {
92
104
  E_JOB_TIMEOUT,
93
105
  E_QUEUE_NOT_INITIALIZED,
94
106
  E_ADAPTER_INIT_ERROR,
107
+ E_INVALID_CRON_EXPRESSION,
108
+ E_INVALID_SCHEDULE_CONFIG,
95
109
  exceptions_exports,
96
110
  DEFAULT_PRIORITY,
97
111
  PRIORITY_SCORE_MULTIPLIER,
@@ -100,4 +114,4 @@ export {
100
114
  DEFAULT_STALLED_THRESHOLD,
101
115
  DEFAULT_ERROR_RETRY_DELAY
102
116
  };
103
- //# sourceMappingURL=chunk-HMGNQSSG.js.map
117
+ //# sourceMappingURL=chunk-SMOKFZ46.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/exceptions.ts","../src/constants.ts"],"sourcesContent":["import { createError } from '@poppinss/utils'\n\nexport const E_INVALID_DURATION_EXPRESSION = createError(\n 'Invalid duration expression: \"%s\"',\n 'E_INVALID_DURATION_EXPRESSION',\n 500\n)\n\nexport const E_INVALID_BASE_DELAY = createError<[reason: string]>(\n 'Invalid base delay. Reason: %s',\n 'E_INVALID_BASE_DELAY',\n 500\n)\n\nexport const E_INVALID_MAX_DELAY = createError<[reason: string]>(\n 'Invalid max delay. Reason: %s',\n 'E_INVALID_MAX_DELAY',\n 500\n)\n\nexport const E_INVALID_MULTIPLIER = createError<[reason: string]>(\n 'Invalid multiplier. Reason: %s',\n 'E_INVALID_MULTIPLIER',\n 500\n)\n\nexport const E_CONFIGURATION_ERROR = createError<[reason: string]>(\n 'Configuration error. Reason: %s',\n 'E_CONFIGURATION_ERROR',\n 500\n)\n\nexport const E_JOB_NOT_FOUND = createError<[jobName: string]>(\n 'Requested job \"%s\" is not registered',\n 'E_JOB_NOT_FOUND'\n)\n\nexport const E_JOB_MAX_ATTEMPTS_REACHED = createError<[jobName: string]>(\n 'The job \"%s\" has reached the maximum number of retry attempts',\n 'E_JOB_MAX_ATTEMPTS_REACHED'\n)\n\nexport const E_JOB_TIMEOUT = createError<[jobName: string, timeout: number]>(\n 'The job \"%s\" has exceeded the timeout of %dms',\n 'E_JOB_TIMEOUT'\n)\n\nexport const E_QUEUE_NOT_INITIALIZED = createError(\n 'QueueManager is not initialized. Call QueueManager.init() before using it.',\n 'E_QUEUE_NOT_INITIALIZED',\n 500\n)\n\nexport const E_ADAPTER_INIT_ERROR = createError<[adapterName: string, originalMessage: string]>(\n 'Failed to initialize adapter \"%s\". Reason: %s',\n 'E_ADAPTER_INIT_ERROR',\n 500\n)\n\nexport const E_NO_JOBS_FOUND = createError<[patterns: string]>(\n 'No jobs found for the specified locations: %s. Verify your glob patterns match your job files.',\n 'E_NO_JOBS_FOUND',\n 500\n)\n\nexport const E_INVALID_CRON_EXPRESSION = createError<[expression: string, reason: string]>(\n 'Invalid cron expression \"%s\": %s',\n 'E_INVALID_CRON_EXPRESSION',\n 500\n)\n\nexport const E_INVALID_SCHEDULE_CONFIG = createError<[reason: string]>(\n 'Invalid schedule configuration: %s',\n 'E_INVALID_SCHEDULE_CONFIG',\n 500\n)\n","/**\n * Default job priority (1-10 scale, lower = higher priority)\n */\nexport const DEFAULT_PRIORITY = 5\n\n/**\n * Multiplier used in score calculation: priority * multiplier + timestamp\n *\n * This ensures higher priority jobs are processed first,\n * while preserving FIFO order within the same priority.\n * The value (1e13) leaves room for ~300 years of millisecond timestamps.\n */\nexport const PRIORITY_SCORE_MULTIPLIER = 1e13\n\n/**\n * Default delay when the worker is idle (no jobs in queue)\n */\nexport const DEFAULT_IDLE_DELAY = '2s'\n\n/**\n * Default interval between stalled job checks\n */\nexport const DEFAULT_STALLED_INTERVAL = '30s'\n\n/**\n * Default threshold after which a job is considered stalled\n */\nexport const DEFAULT_STALLED_THRESHOLD = '30s'\n\n/**\n * Default delay before retrying after an error\n */\nexport const DEFAULT_ERROR_RETRY_DELAY = '5s'\n"],"mappings":";;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAS,mBAAmB;AAErB,IAAM,gCAAgC;AAAA,EAC3C;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,sBAAsB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,wBAAwB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EACA;AACF;AAEO,IAAM,6BAA6B;AAAA,EACxC;AAAA,EACA;AACF;AAEO,IAAM,gBAAgB;AAAA,EAC3B;AAAA,EACA;AACF;AAEO,IAAM,0BAA0B;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,4BAA4B;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,4BAA4B;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AACF;;;ACxEO,IAAM,mBAAmB;AASzB,IAAM,4BAA4B;AAKlC,IAAM,qBAAqB;AAK3B,IAAM,2BAA2B;AAKjC,IAAM,4BAA4B;AAKlC,IAAM,4BAA4B;","names":[]}
@@ -3,7 +3,7 @@ import {
3
3
  E_CONFIGURATION_ERROR,
4
4
  E_JOB_NOT_FOUND,
5
5
  E_QUEUE_NOT_INITIALIZED
6
- } from "./chunk-HMGNQSSG.js";
6
+ } from "./chunk-SMOKFZ46.js";
7
7
 
8
8
  // src/debug.ts
9
9
  import { debuglog } from "util";
@@ -354,4 +354,4 @@ export {
354
354
  Locator,
355
355
  QueueManager
356
356
  };
357
- //# sourceMappingURL=chunk-CD45GT6E.js.map
357
+ //# sourceMappingURL=chunk-US7THLSZ.js.map
@@ -186,6 +186,19 @@ interface Logger {
186
186
  }
187
187
 
188
188
  type Duration = number | string;
189
+ /**
190
+ * Result returned when dispatching a job.
191
+ *
192
+ * @example
193
+ * ```typescript
194
+ * const { jobId } = await SendEmailJob.dispatch(payload)
195
+ * console.log(`Dispatched job: ${jobId}`)
196
+ * ```
197
+ */
198
+ interface DispatchResult {
199
+ /** Unique identifier for this specific job instance */
200
+ jobId: string;
201
+ }
189
202
  interface JobData {
190
203
  id: string;
191
204
  name: string;
@@ -345,6 +358,82 @@ type WorkerCycle = {
345
358
  suggestedDelay: Duration;
346
359
  };
347
360
  type AdapterFactory<T extends Adapter = Adapter> = () => T;
361
+ /**
362
+ * Status of a schedule.
363
+ */
364
+ type ScheduleStatus = 'active' | 'paused';
365
+ /**
366
+ * Configuration for creating a schedule.
367
+ * Used by ScheduleBuilder to collect schedule options before creation.
368
+ */
369
+ interface ScheduleConfig {
370
+ /** Optional ID for the schedule (UUID if not set). Used for upsert. */
371
+ id?: string;
372
+ /** Job class name */
373
+ jobName: string;
374
+ /** Job payload */
375
+ payload: any;
376
+ /** Cron expression (mutually exclusive with everyMs) */
377
+ cronExpression?: string;
378
+ /** Interval in milliseconds (mutually exclusive with cronExpression) */
379
+ everyMs?: number;
380
+ /** IANA timezone for cron evaluation */
381
+ timezone: string;
382
+ /** Start boundary - no jobs dispatched before this */
383
+ from?: Date;
384
+ /** End boundary - no jobs dispatched after this */
385
+ to?: Date;
386
+ /** Maximum number of runs (null = unlimited) */
387
+ limit?: number;
388
+ }
389
+ /**
390
+ * Persisted schedule data.
391
+ * Represents a schedule stored in the adapter.
392
+ */
393
+ interface ScheduleData {
394
+ /** Unique identifier */
395
+ id: string;
396
+ /** Job class name */
397
+ jobName: string;
398
+ /** Job payload */
399
+ payload: any;
400
+ /** Cron expression (null if using interval) */
401
+ cronExpression: string | null;
402
+ /** Interval in milliseconds (null if using cron) */
403
+ everyMs: number | null;
404
+ /** IANA timezone */
405
+ timezone: string;
406
+ /** Start boundary - no jobs dispatched before this */
407
+ from: Date | null;
408
+ /** End boundary - no jobs dispatched after this */
409
+ to: Date | null;
410
+ /** Maximum number of runs */
411
+ limit: number | null;
412
+ /** Number of times this schedule has run */
413
+ runCount: number;
414
+ /** Next scheduled run time */
415
+ nextRunAt: Date | null;
416
+ /** Last run time */
417
+ lastRunAt: Date | null;
418
+ /** Current status */
419
+ status: ScheduleStatus;
420
+ /** When the schedule was created */
421
+ createdAt: Date;
422
+ }
423
+ /**
424
+ * Result returned when creating a schedule.
425
+ */
426
+ interface ScheduleResult {
427
+ /** Unique identifier for the schedule */
428
+ scheduleId: string;
429
+ }
430
+ /**
431
+ * Options for listing schedules.
432
+ */
433
+ interface ScheduleListOptions {
434
+ /** Filter by status */
435
+ status?: ScheduleStatus;
436
+ }
348
437
  interface QueueManagerConfig {
349
438
  default: string;
350
439
  adapters: Record<string, AdapterFactory>;
@@ -504,6 +593,57 @@ interface Adapter {
504
593
  * Called when the worker stops or the adapter is no longer needed.
505
594
  */
506
595
  destroy(): Promise<void>;
596
+ /**
597
+ * Create or update a schedule.
598
+ *
599
+ * If a schedule with the given id exists, it will be updated (upsert).
600
+ * Otherwise, a new schedule is created.
601
+ *
602
+ * @param config - The schedule configuration
603
+ * @returns The schedule ID
604
+ */
605
+ createSchedule(config: ScheduleConfig): Promise<string>;
606
+ /**
607
+ * Get a schedule by ID.
608
+ *
609
+ * @param id - The schedule ID
610
+ * @returns The schedule data, or null if not found
611
+ */
612
+ getSchedule(id: string): Promise<ScheduleData | null>;
613
+ /**
614
+ * List all schedules matching the given options.
615
+ *
616
+ * @param options - Optional filters for listing
617
+ * @returns Array of schedule data
618
+ */
619
+ listSchedules(options?: ScheduleListOptions): Promise<ScheduleData[]>;
620
+ /**
621
+ * Update a schedule's status or run metadata.
622
+ *
623
+ * @param id - The schedule ID
624
+ * @param updates - The fields to update
625
+ */
626
+ updateSchedule(id: string, updates: Partial<Pick<ScheduleData, 'status' | 'nextRunAt' | 'lastRunAt' | 'runCount'>>): Promise<void>;
627
+ /**
628
+ * Delete a schedule permanently.
629
+ *
630
+ * @param id - The schedule ID to delete
631
+ */
632
+ deleteSchedule(id: string): Promise<void>;
633
+ /**
634
+ * Atomically claim a due schedule for execution.
635
+ *
636
+ * This method:
637
+ * 1. Finds ONE schedule where nextRunAt <= now AND status = 'active'
638
+ * 2. Calculates and updates its nextRunAt to the next occurrence
639
+ * 3. Increments runCount and sets lastRunAt
640
+ * 4. Returns the schedule data for job dispatching
641
+ *
642
+ * The atomic nature prevents multiple workers from claiming the same schedule.
643
+ *
644
+ * @returns The claimed schedule, or null if no schedules are due
645
+ */
646
+ claimDueSchedule(): Promise<ScheduleData | null>;
507
647
  }
508
648
 
509
649
  /**
@@ -616,15 +756,15 @@ declare class JobDispatcher<T> {
616
756
  /**
617
757
  * Dispatch the job to the queue.
618
758
  *
619
- * @returns The unique job ID
759
+ * @returns A DispatchResult containing the jobId
620
760
  *
621
761
  * @example
622
762
  * ```typescript
623
- * const jobId = await SendEmailJob.dispatch(payload).run()
763
+ * const { jobId } = await SendEmailJob.dispatch(payload).run()
624
764
  * console.log(`Dispatched job: ${jobId}`)
625
765
  * ```
626
766
  */
627
- run(): Promise<`${string}-${string}-${string}-${string}-${string}`>;
767
+ run(): Promise<DispatchResult>;
628
768
  /**
629
769
  * Thenable implementation for auto-dispatch when awaited.
630
770
  *
@@ -632,9 +772,80 @@ declare class JobDispatcher<T> {
632
772
  *
633
773
  * @param onFulfilled - Success callback
634
774
  * @param onRejected - Error callback
635
- * @returns Promise resolving to the job ID
775
+ * @returns Promise resolving to the DispatchResult
636
776
  */
637
- then(onFulfilled?: (value: string) => any, onRejected?: (reason: any) => any): Promise<any>;
777
+ then(onFulfilled?: (value: DispatchResult) => any, onRejected?: (reason: any) => any): Promise<any>;
778
+ }
779
+
780
+ /**
781
+ * Fluent builder for creating job schedules.
782
+ *
783
+ * @example
784
+ * ```typescript
785
+ * // Create with cron
786
+ * const { scheduleId } = await new ScheduleBuilder('CleanupJob', { days: 30 })
787
+ * .id('cleanup-daily')
788
+ * .cron('0 0 * * *')
789
+ * .timezone('Europe/Paris')
790
+ * .run()
791
+ *
792
+ * // Create with interval
793
+ * const { scheduleId } = await new ScheduleBuilder('SyncJob', {})
794
+ * .every('5m')
795
+ * .run()
796
+ * ```
797
+ */
798
+ declare class ScheduleBuilder implements PromiseLike<ScheduleResult> {
799
+ #private;
800
+ constructor(jobName: string, payload: any);
801
+ /**
802
+ * Set a custom schedule ID.
803
+ * If not specified, defaults to the job name.
804
+ * If a schedule with this ID exists, it will be updated (upsert).
805
+ */
806
+ id(scheduleId: string): this;
807
+ /**
808
+ * Set a cron expression for the schedule.
809
+ * Mutually exclusive with `every()`.
810
+ */
811
+ cron(expression: string): this;
812
+ /**
813
+ * Set a repeating interval for the schedule.
814
+ * Mutually exclusive with `cron()`.
815
+ */
816
+ every(interval: Duration): this;
817
+ /**
818
+ * Set the timezone for cron evaluation.
819
+ * @default 'UTC'
820
+ */
821
+ timezone(tz: string): this;
822
+ /**
823
+ * Set the start boundary for the schedule.
824
+ * No jobs will be dispatched before this date.
825
+ */
826
+ from(date: Date): this;
827
+ /**
828
+ * Set the end boundary for the schedule.
829
+ * No jobs will be dispatched after this date.
830
+ */
831
+ to(date: Date): this;
832
+ /**
833
+ * Set both start and end boundaries for the schedule.
834
+ * Shorthand for `.from(start).to(end)`.
835
+ */
836
+ between(from: Date, to: Date): this;
837
+ /**
838
+ * Set the maximum number of runs for this schedule.
839
+ */
840
+ limit(maxRuns: number): this;
841
+ /**
842
+ * Create the schedule and return the schedule ID.
843
+ */
844
+ run(): Promise<ScheduleResult>;
845
+ /**
846
+ * Implement PromiseLike to allow `await builder.every('5m')` syntax.
847
+ */
848
+ then<TResult1 = ScheduleResult, TResult2 = never>(onfulfilled?: ((value: ScheduleResult) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
638
849
  }
639
850
 
640
851
  /**
@@ -731,6 +942,32 @@ declare abstract class Job<Payload = any> {
731
942
  * ```
732
943
  */
733
944
  static dispatch<T extends Job>(this: new (payload: any, context: JobContext) => T, payload: T extends Job<infer P> ? P : never): JobDispatcher<T extends Job<infer P> ? P : never>;
945
+ /**
946
+ * Create a schedule for this job.
947
+ *
948
+ * Returns a ScheduleBuilder for fluent configuration before creating the schedule.
949
+ * The schedule is not actually created until `.run()` is called or the
950
+ * builder is awaited.
951
+ *
952
+ * @param payload - The data to pass to the job on each run
953
+ * @returns A ScheduleBuilder for fluent configuration
954
+ *
955
+ * @example
956
+ * ```typescript
957
+ * // Cron schedule
958
+ * await CleanupJob.schedule({ days: 30 })
959
+ * .id('cleanup-daily')
960
+ * .cron('0 0 * * *')
961
+ * .timezone('Europe/Paris')
962
+ * .run()
963
+ *
964
+ * // Interval schedule
965
+ * await SyncJob.schedule({ source: 'api' })
966
+ * .every('5m')
967
+ * .run()
968
+ * ```
969
+ */
970
+ static schedule<T extends Job>(this: new (payload: any, context: JobContext) => T, payload: T extends Job<infer P> ? P : never): ScheduleBuilder;
734
971
  /**
735
972
  * Execute the job's business logic.
736
973
  *
@@ -773,4 +1010,4 @@ declare abstract class Job<Payload = any> {
773
1010
  failed?(error: Error): Promise<void>;
774
1011
  }
775
1012
 
776
- export { type Adapter as A, type BackoffStrategy as B, type Duration as D, type JobData as J, type Logger as L, type QueueManagerConfig as Q, type RetryConfig as R, type WorkerCycle as W, type AcquiredJob as a, type JobFactory as b, Job as c, type JobClass as d, customBackoff as e, exponentialBackoff as f, fixedBackoff as g, type JobOptions as h, type JobContext as i, type BackoffConfig as j, type QueueConfig as k, linearBackoff as l, type WorkerConfig as m, type AdapterFactory as n };
1013
+ export { type Adapter as A, type BackoffStrategy as B, type Duration as D, type JobData as J, type Logger as L, type QueueManagerConfig as Q, type RetryConfig as R, type ScheduleConfig as S, type WorkerCycle as W, type AcquiredJob as a, type ScheduleData as b, type ScheduleListOptions as c, type JobFactory as d, Job as e, type JobClass as f, type ScheduleStatus as g, ScheduleBuilder as h, customBackoff as i, exponentialBackoff as j, fixedBackoff as k, linearBackoff as l, type DispatchResult as m, type JobOptions as n, type JobContext as o, type BackoffConfig as p, type QueueConfig as q, type WorkerConfig as r, type AdapterFactory as s, type ScheduleResult as t };
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-2Ng_OpVK.js';
2
+ export { h as ScheduleBuilder, i as customBackoff, j as exponentialBackoff, k as fixedBackoff, l as linearBackoff } from './index-2Ng_OpVK.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 jobName(): 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 };