@@ -10,6 +10,8 @@ import { Metrics } from './metrics'
1010import { StalledJobChecker } from './stalled-checker'
1111import { generateId , getRedisClient , mergeOptions } from './utils'
1212import { Worker } from './worker'
13+ import { RateLimiter } from './rate-limiter'
14+ import type { RateLimitResult } from './rate-limiter'
1315
1416export class Queue < T = any > {
1517 name : string
@@ -22,13 +24,16 @@ export class Queue<T = any> {
2224 private cleanupService : CleanupService | null = null
2325 private stalledChecker : StalledJobChecker | null = null
2426 private readonly logger = createLogger ( )
27+ private limiter : RateLimiter | null = null
28+ private defaultJobOptions : JobOptions | undefined
2529
2630 constructor ( name : string , options ?: QueueConfig ) {
2731 this . name = name
2832 this . prefix = options ?. prefix || config . prefix || 'queue'
2933 this . redisClient = getRedisClient ( options )
3034 this . keyPrefix = `${ this . prefix } :${ this . name } `
3135 this . events = new JobEvents ( name )
36+ this . defaultJobOptions = options ?. defaultJobOptions
3237
3338 // Set logger level if specified
3439 if ( options ?. logLevel ) {
@@ -51,6 +56,12 @@ export class Queue<T = any> {
5156 this . logger . debug ( `Stalled job checker started for queue ${ name } ` )
5257 }
5358
59+ // Initialize rate limiter if provided
60+ if ( options ?. limiter ) {
61+ this . limiter = new RateLimiter ( this , options . limiter )
62+ this . logger . debug ( `Rate limiter configured for queue ${ name } ` )
63+ }
64+
5465 // Initialize scripts
5566 this . init ( )
5667 }
@@ -71,10 +82,29 @@ export class Queue<T = any> {
7182 }
7283
7384 /**
74- * Add a job to the queue
85+ * Add a job to the queue with rate limiting support
7586 */
7687 async add ( data : T , options ?: JobOptions ) : Promise < Job < T > > {
7788 try {
89+ // Check rate limit if configured
90+ if ( this . limiter ) {
91+ // If we have keyPrefix in the limiter, check rate limit based on data
92+ const limiterResult = await this . limiter . check ( data )
93+
94+ if ( limiterResult . limited ) {
95+ this . logger . warn ( `Rate limit exceeded, retrying in ${ limiterResult . resetIn } ms` )
96+
97+ // If rate limited, add to delayed queue with the reset time
98+ const opts = {
99+ ...this . defaultJobOptions ,
100+ ...options ,
101+ delay : limiterResult . resetIn
102+ }
103+
104+ return this . add ( data , opts )
105+ }
106+ }
107+
78108 const opts = mergeOptions ( options )
79109 const jobId = opts . jobId || generateId ( )
80110 const timestamp = Date . now ( )
@@ -492,4 +522,28 @@ export class Queue<T = any> {
492522 getJobKey ( jobId : string ) : string {
493523 return `${ this . keyPrefix } :job:${ jobId } `
494524 }
525+
526+ /**
527+ * Check if the queue is rate limited for a specific key
528+ */
529+ async isRateLimited ( key ?: string , data ?: T ) : Promise < { limited : boolean ; resetIn : number } > {
530+ if ( ! this . limiter ) {
531+ return { limited : false , resetIn : 0 }
532+ }
533+
534+ let result : RateLimitResult
535+
536+ if ( key ) {
537+ // Use explicit key if provided
538+ result = await this . limiter . checkByKey ( key )
539+ } else {
540+ // Use data with keyPrefix from limiter options
541+ result = await this . limiter . check ( data )
542+ }
543+
544+ return {
545+ limited : result . limited ,
546+ resetIn : result . resetIn ,
547+ }
548+ }
495549}
0 commit comments