@@ -27,6 +27,7 @@ export type QueueItem<TMessageCatalog extends MessageCatalogSchema> = {
27
27
visibilityTimeoutMs : number ;
28
28
attempt : number ;
29
29
timestamp : Date ;
30
+ deduplicationKey ?: string ;
30
31
} ;
31
32
32
33
export type AnyQueueItem = {
@@ -36,6 +37,7 @@ export type AnyQueueItem = {
36
37
visibilityTimeoutMs : number ;
37
38
attempt : number ;
38
39
timestamp : Date ;
40
+ deduplicationKey ?: string ;
39
41
} ;
40
42
41
43
export class SimpleQueue < TMessageCatalog extends MessageCatalogSchema > {
@@ -98,11 +100,13 @@ export class SimpleQueue<TMessageCatalog extends MessageCatalogSchema> {
98
100
} ) : Promise < void > {
99
101
try {
100
102
const score = availableAt ? availableAt . getTime ( ) : Date . now ( ) ;
103
+ const deduplicationKey = nanoid ( ) ;
101
104
const serializedItem = JSON . stringify ( {
102
105
job,
103
106
item,
104
107
visibilityTimeoutMs,
105
108
attempt,
109
+ deduplicationKey,
106
110
} ) ;
107
111
108
112
const result = await this . redis . enqueueItem (
@@ -136,7 +140,7 @@ export class SimpleQueue<TMessageCatalog extends MessageCatalogSchema> {
136
140
return [ ] ;
137
141
}
138
142
139
- const dequeuedItems = [ ] ;
143
+ const dequeuedItems : Array < QueueItem < TMessageCatalog > > = [ ] ;
140
144
141
145
for ( const [ id , serializedItem , score ] of results ) {
142
146
const parsedItem = JSON . parse ( serializedItem ) as any ;
@@ -186,6 +190,7 @@ export class SimpleQueue<TMessageCatalog extends MessageCatalogSchema> {
186
190
visibilityTimeoutMs,
187
191
attempt : parsedItem . attempt ?? 0 ,
188
192
timestamp,
193
+ deduplicationKey : parsedItem . deduplicationKey ,
189
194
} ) ;
190
195
}
191
196
@@ -200,14 +205,22 @@ export class SimpleQueue<TMessageCatalog extends MessageCatalogSchema> {
200
205
}
201
206
}
202
207
203
- async ack ( id : string ) : Promise < void > {
208
+ async ack ( id : string , deduplicationKey ?: string ) : Promise < void > {
204
209
try {
205
- await this . redis . ackItem ( `queue` , `items` , id ) ;
210
+ const result = await this . redis . ackItem ( `queue` , `items` , id , deduplicationKey ?? "" ) ;
211
+ if ( result === 0 ) {
212
+ this . logger . error ( `SimpleQueue ${ this . name } .ack(): ack operation returned 0` , {
213
+ queue : this . name ,
214
+ id,
215
+ deduplicationKey,
216
+ } ) ;
217
+ }
206
218
} catch ( e ) {
207
219
this . logger . error ( `SimpleQueue ${ this . name } .ack(): error acknowledging item` , {
208
220
queue : this . name ,
209
221
error : e ,
210
222
id,
223
+ deduplicationKey,
211
224
} ) ;
212
225
throw e ;
213
226
}
@@ -367,15 +380,32 @@ export class SimpleQueue<TMessageCatalog extends MessageCatalogSchema> {
367
380
this . redis . defineCommand ( "ackItem" , {
368
381
numberOfKeys : 2 ,
369
382
lua : `
370
- local queue = KEYS[1]
371
- local items = KEYS[2]
383
+ local queueKey = KEYS[1]
384
+ local itemsKey = KEYS[2]
372
385
local id = ARGV[1]
386
+ local deduplicationKey = ARGV[2]
373
387
374
- redis.call('ZREM', queue, id)
375
- redis.call('HDEL', items, id)
388
+ -- Get the item from the hash
389
+ local item = redis.call('HGET', itemsKey, id)
390
+ if not item then
391
+ return -1
392
+ end
376
393
394
+ -- Only check deduplicationKey if a non-empty one was passed in
395
+ if deduplicationKey and deduplicationKey ~= "" then
396
+ local success, parsed = pcall(cjson.decode, item)
397
+ if success then
398
+ if parsed.deduplicationKey and parsed.deduplicationKey ~= deduplicationKey then
399
+ return 0
400
+ end
401
+ end
402
+ end
403
+
404
+ -- Remove from sorted set and hash
405
+ redis.call('ZREM', queueKey, id)
406
+ redis.call('HDEL', itemsKey, id)
377
407
return 1
378
- ` ,
408
+ ` ,
379
409
} ) ;
380
410
381
411
this . redis . defineCommand ( "moveToDeadLetterQueue" , {
@@ -468,6 +498,7 @@ declare module "@internal/redis" {
468
498
queue : string ,
469
499
items : string ,
470
500
id : string ,
501
+ deduplicationKey : string ,
471
502
callback ?: Callback < number >
472
503
) : Result < number , Context > ;
473
504
0 commit comments