qdone 2.0.34-alpha → 2.0.36-alpha

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/src/defaults.js CHANGED
@@ -18,11 +18,16 @@ export const defaults = Object.freeze({
18
18
  fifo: false,
19
19
  disableLog: false,
20
20
  includeFailed: false,
21
+ includeDead: false,
22
+ externalDedup: false,
23
+ dedupPeriod: 60 * 5,
24
+ dedupStats: false,
21
25
 
22
26
  // Enqueue
23
27
  groupId: uuidv1(),
24
28
  groupIdPerMessage: false,
25
29
  deduplicationId: undefined,
30
+ dedupIdPerMessage: false,
26
31
  messageRetentionPeriod: 1209600,
27
32
  delay: 0,
28
33
  sendRetries: 6,
@@ -42,7 +47,10 @@ export const defaults = Object.freeze({
42
47
  // Idle Queues
43
48
  idleFor: 60,
44
49
  delete: false,
45
- unpair: false
50
+ unpair: false,
51
+
52
+ // Check
53
+ create: false
46
54
  })
47
55
 
48
56
  function validateInteger (opt, name) {
@@ -51,6 +59,19 @@ function validateInteger (opt, name) {
51
59
  return parsed
52
60
  }
53
61
 
62
+ export function validateMessageOptions (messageOptions) {
63
+ const validKeys = ['deduplicaitonId', 'groupId']
64
+ if (typeof messageOptions === 'object' &&
65
+ !Array.isArray(messageOptions) &&
66
+ messageOptions !== null) {
67
+ for (const key in messageOptions) {
68
+ if (!validKeys.includes(key)) throw new Error(`Invalid message option ${key}`)
69
+ }
70
+ return messageOptions
71
+ }
72
+ return {}
73
+ }
74
+
54
75
  /**
55
76
  * This function should be called by each exposed API entry point on the
56
77
  * options passed in from the caller. It supports options named in camelCase
@@ -64,7 +85,7 @@ export function getOptionsWithDefaults (options) {
64
85
  if (!options) options = {}
65
86
 
66
87
  // Activate DLQ if any option is set
67
- const dlq = options.dlq || !!(options['dlq-suffix'] || options['dlq-after'] || options['dlq-name'])
88
+ const dlq = options.dlq || !!(options['dlq-suffix'] || options['dlq-after'] || options['dlq-name'] || options.dlqSuffix || options.dlqAfter || options.dlqName)
68
89
 
69
90
  const opt = {
70
91
  // Shared
@@ -76,6 +97,11 @@ export function getOptionsWithDefaults (options) {
76
97
  fifo: options.fifo || defaults.fifo,
77
98
  sentryDsn: options.sentryDsn || options['sentry-dsn'],
78
99
  disableLog: options.disableLog || options['disable-log'] || defaults.disableLog,
100
+ includeFailed: options.includeFailed || options['include-failed'] || defaults.includeFailed,
101
+ includeDead: options.includeDead || options['include-dead'] || defaults.includeDead,
102
+ externalDedup: options.externalDedup || options['external-dedup'] || defaults.externalDedup,
103
+ dedupPeriod: options.dedupPeriod || options['dedup-period'] || defaults.dedupPeriod,
104
+ dedupStats: options.dedupStats || options['dedup-stats'] || defaults.dedupStats,
79
105
 
80
106
  // Cache
81
107
  cacheUri: options.cacheUri || options['cache-uri'] || defaults.cacheUri,
@@ -86,6 +112,7 @@ export function getOptionsWithDefaults (options) {
86
112
  groupId: options.groupId || options['group-id'] || defaults.groupId,
87
113
  groupIdPerMessage: false,
88
114
  deduplicationId: options.deduplicationId || options['deduplication-id'] || defaults.deduplicationId,
115
+ dedupIdPerMessage: options.dedupIdPerMessage || options['dedup-id-per-message'] || defaults.dedupIdPerMessage,
89
116
  messageRetentionPeriod: options.messageRetentionPeriod || options['message-retention-period'] || defaults.messageRetentionPeriod,
90
117
  delay: options.delay || defaults.delay,
91
118
  sendRetries: options['send-retries'] || defaults.sendRetries,
@@ -100,14 +127,17 @@ export function getOptionsWithDefaults (options) {
100
127
  killAfter: options.killAfter || options['kill-after'] || defaults.killAfter,
101
128
  archive: options.archive || defaults.archive,
102
129
  activeOnly: options.activeOnly || options['active-only'] || defaults.activeOnly,
103
- includeFailed: options.includeFailed || options['include-failed'] || defaults.includeFailed,
104
130
  maxConcurrentJobs: options.maxConcurrentJobs || defaults.maxConcurrentJobs,
105
131
  maxMemoryPercent: options.maxMemoryPercent || defaults.maxMemoryPercent,
106
132
 
107
133
  // Idle Queues
108
134
  idleFor: options.idleFor || options['idle-for'] || defaults.idleFor,
109
135
  delete: options.delete || defaults.delete,
110
- unpair: options.delete || defaults.unpair
136
+ unpair: options.delete || defaults.unpair,
137
+
138
+ // Check
139
+ create: options.create || defaults.create,
140
+ overwrite: options.overwrite || defaults.overwrite
111
141
  }
112
142
 
113
143
  // Setting this env here means we don't have to in AWS SDK constructors
@@ -118,6 +148,7 @@ export function getOptionsWithDefaults (options) {
118
148
  opt.messageRetentionPeriod = validateInteger(opt, 'messageRetentionPeriod')
119
149
  opt.delay = validateInteger(opt, 'delay')
120
150
  opt.sendRetries = validateInteger(opt, 'sendRetries')
151
+ opt.dedupPeriod = validateInteger(opt, 'dedupPeriod')
121
152
  opt.failDelay = validateInteger(opt, 'failDelay')
122
153
  opt.dlqAfter = validateInteger(opt, 'dlqAfter')
123
154
  opt.waitTime = validateInteger(opt, 'waitTime')
@@ -126,6 +157,11 @@ export function getOptionsWithDefaults (options) {
126
157
  opt.maxMemoryPercent = validateInteger(opt, 'maxMemoryPercent')
127
158
  opt.idleFor = validateInteger(opt, 'idleFor')
128
159
 
160
+ // Validate dedup args
161
+ if (opt.externalDedup && !opt.cacheUri) throw new Error('--external-dedup requires the --cache-uri argument')
162
+ if (opt.externalDedup && (!opt.dedupPeriod || opt.dedupPeriod < 1)) throw new Error('--external-dedup of redis requires a --dedup-period > 1 second')
163
+ if (opt.dedupIdPerMessage && opt.deduplicationId) throw new Error('Use either --deduplication-id or --dedup-id-per-message but not both')
164
+
129
165
  return opt
130
166
  }
131
167
 
package/src/enqueue.js CHANGED
@@ -1,4 +1,4 @@
1
- import { addBreadcrumb } from '@sentry/node'
1
+ import { addBreadcrumb, setExtra } from '@sentry/node'
2
2
  import { v1 as uuidV1 } from 'uuid'
3
3
  import chalk from 'chalk'
4
4
  import Debug from 'debug'
@@ -20,14 +20,31 @@ import {
20
20
  normalizeDLQName
21
21
  } from './qrlCache.js'
22
22
  import { getSQSClient } from './sqs.js'
23
- import { getOptionsWithDefaults } from './defaults.js'
23
+ import {
24
+ addDedupParamsToMessage,
25
+ dedupShouldEnqueue,
26
+ dedupShouldEnqueueMulti,
27
+ dedupSuccessfullyProcessed
28
+ } from './dedup.js'
29
+ import { getOptionsWithDefaults, validateMessageOptions } from './defaults.js'
24
30
  import { ExponentialBackoff } from './exponentialBackoff.js'
25
31
 
26
32
  const debug = Debug('qdone:enqueue')
27
33
 
34
+ export function getDLQParams (queue, opt) {
35
+ const dqname = normalizeDLQName(queue, opt)
36
+ const params = {
37
+ Attributes: { MessageRetentionPeriod: opt.messageRetentionPeriod + '' },
38
+ QueueName: dqname
39
+ }
40
+ if (opt.tags) params.tags = opt.tags
41
+ if (opt.fifo) params.Attributes.FifoQueue = 'true'
42
+ return { dqname, params }
43
+ }
44
+
28
45
  export async function getOrCreateDLQ (queue, opt) {
29
46
  debug('getOrCreateDLQ(', queue, ')')
30
- const dqname = normalizeDLQName(queue, opt)
47
+ const { dqname, params } = getDLQParams(queue, opt)
31
48
  try {
32
49
  const dqrl = await qrlCacheGet(dqname)
33
50
  return dqrl
@@ -37,12 +54,6 @@ export async function getOrCreateDLQ (queue, opt) {
37
54
 
38
55
  // Create our DLQ
39
56
  const client = getSQSClient()
40
- const params = {
41
- Attributes: { MessageRetentionPeriod: opt.messageRetentionPeriod + '' },
42
- QueueName: dqname
43
- }
44
- if (opt.tags) params.tags = opt.tags
45
- if (opt.fifo) params.Attributes.FifoQueue = 'true'
46
57
  const cmd = new CreateQueueCommand(params)
47
58
  if (opt.verbose) console.error(chalk.blue('Creating dead letter queue ') + dqname)
48
59
  const data = await client.send(cmd)
@@ -53,35 +64,62 @@ export async function getOrCreateDLQ (queue, opt) {
53
64
  }
54
65
  }
55
66
 
56
- export async function getOrCreateFailQueue (queue, opt) {
67
+ /**
68
+ * Returns the parameters needed for creating a failed queue. If DLQ options
69
+ * are set, it makes an API call to get this DLQ's ARN.
70
+ */
71
+ export async function getFailParams (queue, opt) {
72
+ const fqname = normalizeFailQueueName(queue, opt)
73
+ const params = {
74
+ Attributes: { MessageRetentionPeriod: opt.messageRetentionPeriod + '' },
75
+ QueueName: fqname
76
+ }
77
+ // If we have a dlq, we grab it and set a redrive policy
78
+ if (opt.dlq) {
79
+ const dqname = normalizeDLQName(queue, opt)
80
+ const dqrl = await qrlCacheGet(dqname)
81
+ const dqa = await getQueueAttributes(dqrl)
82
+ debug('dqa', dqa)
83
+ params.Attributes.RedrivePolicy = JSON.stringify({
84
+ deadLetterTargetArn: dqa.Attributes.QueueArn,
85
+ maxReceiveCount: opt.dlqAfter
86
+ })
87
+ }
88
+ if (opt.failDelay) params.Attributes.DelaySeconds = opt.failDelay + ''
89
+ if (opt.tags) params.tags = opt.tags
90
+ if (opt.fifo) params.Attributes.FifoQueue = 'true'
91
+ return params
92
+ }
93
+
94
+ /**
95
+ * Returns the qrl for the failed queue for the given queue. Creates the queue
96
+ * if it does not exist.
97
+ */
98
+ export async function getOrCreateFailQueue (queue, opt, doesNotExist) {
57
99
  debug('getOrCreateFailQueue(', queue, ')')
58
100
  const fqname = normalizeFailQueueName(queue, opt)
59
101
  try {
102
+ // Bail early if the caller knew we didn't have a queue
103
+ if (doesNotExist) throw new QueueDoesNotExist(fqname)
60
104
  const fqrl = await qrlCacheGet(fqname)
61
105
  return fqrl
62
106
  } catch (err) {
63
107
  // Anything other than queue doesn't exist gets re-thrown
64
108
  if (!(err instanceof QueueDoesNotExist)) throw err
65
109
 
66
- // Crate our fail queue
67
- const client = getSQSClient()
68
- const params = {
69
- Attributes: { MessageRetentionPeriod: opt.messageRetentionPeriod + '' },
70
- QueueName: fqname
71
- }
72
- // If we have a dlq, we grab it and set a redrive policy
73
- if (opt.dlq) {
74
- const dqrl = await getOrCreateDLQ(queue, opt)
75
- const dqa = await getQueueAttributes(dqrl)
76
- debug('dqa', dqa)
77
- params.Attributes.RedrivePolicy = JSON.stringify({
78
- deadLetterTargetArn: dqa.Attributes.QueueArn,
79
- maxReceiveCount: opt.dlqAfter + ''
80
- })
110
+ // Grab params, creating DLQ if needed
111
+ let params
112
+ try {
113
+ params = await getFailParams(queue, opt)
114
+ } catch (e) {
115
+ // If DLQ doesn't exist, create it
116
+ if (!(opt.dlq && e instanceof QueueDoesNotExist)) throw e
117
+ await getOrCreateDLQ(queue, opt)
118
+ params = await getFailParams(queue, opt)
81
119
  }
82
- if (opt.failDelay) params.Attributes.DelaySeconds = opt.failDelay + ''
83
- if (opt.tags) params.tags = opt.tags
84
- if (opt.fifo) params.Attributes.FifoQueue = 'true'
120
+
121
+ // Create our fail queue
122
+ const client = getSQSClient()
85
123
  const cmd = new CreateQueueCommand(params)
86
124
  if (opt.verbose) console.error(chalk.blue('Creating fail queue ') + fqname)
87
125
  const data = await client.send(cmd)
@@ -92,6 +130,30 @@ export async function getOrCreateFailQueue (queue, opt) {
92
130
  }
93
131
  }
94
132
 
133
+ /**
134
+ * Returns the parameters needed for creating a queue. If fail options
135
+ * are set, it makes an API call to get the fail queue's ARN.
136
+ */
137
+ export async function getQueueParams (queue, opt) {
138
+ const qname = normalizeQueueName(queue, opt)
139
+ const fqname = normalizeFailQueueName(queue, opt)
140
+ const fqrl = await qrlCacheGet(fqname, opt)
141
+ const fqa = await getQueueAttributes(fqrl)
142
+ const params = {
143
+ Attributes: {
144
+ MessageRetentionPeriod: opt.messageRetentionPeriod + '',
145
+ RedrivePolicy: JSON.stringify({
146
+ deadLetterTargetArn: fqa.Attributes.QueueArn,
147
+ maxReceiveCount: 1
148
+ })
149
+ },
150
+ QueueName: qname
151
+ }
152
+ if (opt.tags) params.tags = opt.tags
153
+ if (opt.fifo) params.Attributes.FifoQueue = 'true'
154
+ return params
155
+ }
156
+
95
157
  /**
96
158
  * Returns a qrl for a queue that either exists or does not
97
159
  */
@@ -105,29 +167,25 @@ export async function getOrCreateQueue (queue, opt) {
105
167
  // Anything other than queue doesn't exist gets re-thrown
106
168
  if (!(err instanceof QueueDoesNotExist)) throw err
107
169
 
108
- // Get our fail queue so we can create our own
109
- const fqrl = await getOrCreateFailQueue(qname, opt)
110
- const fqa = await getQueueAttributes(fqrl)
170
+ // Grab params, creating fail queue if needed
171
+ let params
172
+ try {
173
+ params = await getQueueParams(qname, opt)
174
+ } catch (e) {
175
+ // If fail queue doesn't exist, create it
176
+ if (!(e instanceof QueueDoesNotExist)) throw e
177
+ await getOrCreateFailQueue(qname, opt, true)
178
+ params = await getQueueParams(qname, opt)
179
+ }
180
+
181
+ debug({ getOrCreateQueue: { qname, params } })
111
182
 
112
183
  // Create our queue
113
184
  const client = getSQSClient()
114
- const params = {
115
- Attributes: {
116
- MessageRetentionPeriod: opt.messageRetentionPeriod + '',
117
- RedrivePolicy: JSON.stringify({
118
- deadLetterTargetArn: fqa.Attributes.QueueArn,
119
- maxReceiveCount: '1'
120
- })
121
- },
122
- QueueName: qname
123
- }
124
- if (opt.tags) params.tags = opt.tags
125
- if (opt.fifo) params.Attributes.FifoQueue = 'true'
126
185
  const cmd = new CreateQueueCommand(params)
127
- debug({ params })
128
- if (opt.verbose) console.error(chalk.blue('Creating queue ') + qname)
186
+ if (opt.verbose) console.error(chalk.blue('Creating fail queue ') + qname)
129
187
  const data = await client.send(cmd)
130
- debug('createQueue returned', data)
188
+ debug('AWS createQueue returned', data)
131
189
  const qrl = data.QueueUrl
132
190
  qrlCacheSet(qname, qrl)
133
191
  return qrl
@@ -145,17 +203,15 @@ export async function getQueueAttributes (qrl) {
145
203
  return data
146
204
  }
147
205
 
148
- export function formatMessage (command, id) {
149
- const message = {
150
- /*
151
- MessageAttributes: {
152
- City: { DataType: 'String', StringValue: 'Any City' },
153
- Population: { DataType: 'Number', StringValue: '1250800' }
154
- },
155
- */
156
- MessageBody: command
157
- }
206
+ export function formatMessage (body, id, opt, messageOptions) {
207
+ const message = { MessageBody: body }
158
208
  if (typeof id !== 'undefined') message.Id = '' + id
209
+ if (opt.fifo) {
210
+ message.MessageGroupId = messageOptions?.groupId || opt?.groupId
211
+ }
212
+ addDedupParamsToMessage(message, opt, messageOptions)
213
+ if (opt.delay) message.DelaySeconds = opt.delay
214
+ if (messageOptions?.delay) message.DelaySeconds = messageOptions.delay
159
215
  return message
160
216
  }
161
217
 
@@ -166,14 +222,19 @@ const retryableExceptions = [
166
222
  QueueDoesNotExist // Queue could temporarily not exist due to eventual consistency, let it retry
167
223
  ]
168
224
 
169
- export async function sendMessage (qrl, command, opt) {
225
+ export async function sendMessage (qrl, command, opt, messageOptions) {
170
226
  debug('sendMessage(', qrl, command, ')')
171
- const params = Object.assign({ QueueUrl: qrl }, formatMessage(command))
172
- if (opt.fifo) {
173
- params.MessageGroupId = opt.groupId
174
- params.MessageDeduplicationId = opt.deduplicationId || uuidV1()
227
+ const uuidFunction = opt.uuidFunction || uuidV1
228
+ const params = {
229
+ QueueUrl: qrl,
230
+ ...formatMessage(command, null, opt, messageOptions)
231
+ }
232
+
233
+ // See if we even have to send it
234
+ if (opt.externalDedup) {
235
+ const shouldEnqueue = await dedupShouldEnqueue(params, opt)
236
+ if (!shouldEnqueue) return { MessageId: uuidFunction() }
175
237
  }
176
- if (opt.delay) params.DelaySeconds = opt.delay
177
238
 
178
239
  // Send it
179
240
  const client = getSQSClient()
@@ -187,12 +248,15 @@ export async function sendMessage (qrl, command, opt) {
187
248
  return data
188
249
  }
189
250
  const shouldRetry = async (result, error) => {
251
+ if (!error) return false
190
252
  for (const exceptionClass of retryableExceptions) {
191
253
  if (error instanceof exceptionClass) {
192
254
  debug({ sendMessageRetryingBecause: { error, result } })
193
255
  return true
194
256
  }
195
257
  }
258
+ // If we could not send it, we also need to remove our dedup flag
259
+ await dedupSuccessfullyProcessed(params, opt)
196
260
  return false
197
261
  }
198
262
  const result = await backoff.run(send, shouldRetry)
@@ -203,25 +267,17 @@ export async function sendMessage (qrl, command, opt) {
203
267
  export async function sendMessageBatch (qrl, messages, opt) {
204
268
  debug('sendMessageBatch(', qrl, messages.map(e => Object.assign(Object.assign({}, e), { MessageBody: e.MessageBody.slice(0, 10) + '...' })), ')')
205
269
  const params = { Entries: messages, QueueUrl: qrl }
206
- const uuidFunction = opt.uuidFunction || uuidV1
207
- // Add in group id if we're using fifo
208
- if (opt.fifo) {
209
- params.Entries = params.Entries.map(
210
- message => Object.assign({
211
- MessageGroupId: opt.groupIdPerMessage ? uuidFunction() : opt.groupId,
212
- MessageDeduplicationId: opt.deduplicationId || uuidFunction()
213
- }, message)
214
- )
215
- }
216
- if (opt.delay) {
217
- params.Entries = params.Entries.map(message =>
218
- Object.assign({ DelaySeconds: opt.delay }, message))
219
- }
220
270
  if (opt.sentryDsn) {
221
271
  addBreadcrumb({ category: 'sendMessageBatch', message: JSON.stringify({ params }), level: 'debug' })
222
272
  }
223
273
  debug({ params })
224
274
 
275
+ // See which messages we even have to send
276
+ if (opt.externalDedup) {
277
+ params.Entries = await dedupShouldEnqueueMulti(params.Entries, opt)
278
+ if (!params.Entries.length) return
279
+ }
280
+
225
281
  // Send them
226
282
  const client = getSQSClient()
227
283
  const cmd = new SendMessageBatchCommand(params)
@@ -320,8 +376,8 @@ export async function flushMessages (qrl, opt, sendBuffer) {
320
376
  // Automaticaly flushes if queue has >= 10 messages.
321
377
  // Returns number of messages flushed.
322
378
  //
323
- export async function addMessage (qrl, command, messageIndex, opt, sendBuffer) {
324
- const message = formatMessage(command, messageIndex)
379
+ export async function addMessage (qrl, command, messageIndex, opt, sendBuffer, messageOptions) {
380
+ const message = formatMessage(command, messageIndex, opt, messageOptions)
325
381
  sendBuffer[qrl] = sendBuffer[qrl] || []
326
382
  sendBuffer[qrl].push(message)
327
383
  debug({ location: 'addMessage', sendBuffer })
@@ -338,8 +394,16 @@ export async function addMessage (qrl, command, messageIndex, opt, sendBuffer) {
338
394
  export async function enqueue (queue, command, options) {
339
395
  debug('enqueue(', { queue, command }, ')')
340
396
  const opt = getOptionsWithDefaults(options)
341
- const qrl = await getOrCreateQueue(queue, opt)
342
- return sendMessage(qrl, command, opt)
397
+ if (opt.sentryDsn) {
398
+ setExtra({ qdoneOperation: 'enqueue', args: { queue, command, opt } })
399
+ }
400
+ try {
401
+ const qrl = await getOrCreateQueue(queue, opt)
402
+ return sendMessage(qrl, command, opt)
403
+ } catch (e) {
404
+ console.log(e)
405
+ throw e
406
+ }
343
407
  }
344
408
 
345
409
  //
@@ -349,43 +413,50 @@ export async function enqueue (queue, command, options) {
349
413
  export async function enqueueBatch (pairs, options) {
350
414
  debug('enqueueBatch(', pairs, ')')
351
415
  const opt = getOptionsWithDefaults(options)
352
-
353
- // Find unique queues so we can pre-fetch qrls. We do this so that all
354
- // queues are created prior to going through our flush logic
355
- const normalizedPairs = pairs.map(({ queue, command }) => ({
356
- qname: normalizeQueueName(queue, opt),
357
- command
358
- }))
359
- const uniqueQnames = new Set(normalizedPairs.map(p => p.qname))
360
-
361
- // Prefetch qrls / create queues in parallel
362
- const createPromises = []
363
- for (const qname of uniqueQnames) {
364
- createPromises.push(getOrCreateQueue(qname, opt))
365
- }
366
- await Promise.all(createPromises)
367
-
368
- // After we've prefetched, all qrls are in cache
369
- // so go back through the list of pairs and fire off messages
370
- requestCount = 0
371
- const sendBuffer = {}
372
- let messageIndex = 0
373
- let initialFlushTotal = 0
374
- for (const { qname, command } of normalizedPairs) {
375
- const qrl = await getOrCreateQueue(qname, opt)
376
- initialFlushTotal += await addMessage(qrl, command, messageIndex++, opt, sendBuffer)
416
+ if (opt.sentryDsn) {
417
+ setExtra({ qdoneOperation: 'enqueueBatch', args: { pairs, opt } })
377
418
  }
419
+ try {
420
+ // Find unique queues so we can pre-fetch qrls. We do this so that all
421
+ // queues are created prior to going through our flush logic
422
+ const normalizedPairs = pairs.map(({ queue, command, messageOptions }) => ({
423
+ qname: normalizeQueueName(queue, opt),
424
+ command,
425
+ messageOptions: validateMessageOptions(messageOptions)
426
+ }))
427
+ const uniqueQnames = new Set(normalizedPairs.map(p => p.qname))
378
428
 
379
- // And flush any remaining messages
380
- const extraFlushPromises = []
381
- for (const qrl in sendBuffer) {
382
- extraFlushPromises.push(flushMessages(qrl, opt, sendBuffer))
429
+ // Prefetch qrls / create queues in parallel
430
+ const createPromises = []
431
+ for (const qname of uniqueQnames) {
432
+ createPromises.push(getOrCreateQueue(qname, opt))
433
+ }
434
+ await Promise.all(createPromises)
435
+ // After we've prefetched, all qrls are in cache
436
+ // so go back through the list of pairs and fire off messages
437
+ requestCount = 0
438
+ const sendBuffer = {}
439
+ let messageIndex = 0
440
+ let initialFlushTotal = 0
441
+ for (const { qname, command, messageOptions } of normalizedPairs) {
442
+ const qrl = await getOrCreateQueue(qname, opt)
443
+ initialFlushTotal += await addMessage(qrl, command, messageIndex++, opt, sendBuffer, messageOptions)
444
+ }
445
+
446
+ // And flush any remaining messages
447
+ const extraFlushPromises = []
448
+ for (const qrl in sendBuffer) {
449
+ extraFlushPromises.push(flushMessages(qrl, opt, sendBuffer))
450
+ }
451
+ const extraFlushCounts = await Promise.all(extraFlushPromises)
452
+ const extraFlushTotal = extraFlushCounts.reduce((a, b) => a + b, 0)
453
+ const totalFlushed = initialFlushTotal + extraFlushTotal
454
+ debug({ initialFlushTotal, extraFlushTotal, totalFlushed })
455
+ return totalFlushed
456
+ } catch (e) {
457
+ console.log(e)
458
+ throw e
383
459
  }
384
- const extraFlushCounts = await Promise.all(extraFlushPromises)
385
- const extraFlushTotal = extraFlushCounts.reduce((a, b) => a + b, 0)
386
- const totalFlushed = initialFlushTotal + extraFlushTotal
387
- debug({ initialFlushTotal, extraFlushTotal, totalFlushed })
388
- return totalFlushed
389
460
  }
390
461
 
391
462
  debug('loaded')
package/src/qrlCache.js CHANGED
@@ -66,7 +66,7 @@ export async function qrlCacheGet (qname) {
66
66
  // debug({ cmd })
67
67
  const result = await client.send(cmd)
68
68
  // debug('result', result)
69
- // if (!result) throw new Error(`No such queue ${qname}`)
69
+ if (!result) throw new QueueDoesNotExist(qname)
70
70
  const { QueueUrl: qrl } = result
71
71
  // debug('getQueueUrl returned', data)
72
72
  qcache.set(qname, qrl)
@@ -8,6 +8,7 @@ import { ChangeMessageVisibilityBatchCommand, DeleteMessageBatchCommand } from '
8
8
  import chalk from 'chalk'
9
9
  import Debug from 'debug'
10
10
 
11
+ import { dedupSuccessfullyProcessedMulti } from '../dedup.js'
11
12
  import { getSQSClient } from '../sqs.js'
12
13
 
13
14
  const debug = Debug('qdone:jobExecutor')
@@ -221,6 +222,10 @@ export class JobExecutor {
221
222
  }
222
223
  }
223
224
  debug('DeleteMessageBatch returned', result)
225
+
226
+ // Mark batch as processed for dedup
227
+ await dedupSuccessfullyProcessedMulti(entries.map(e => this.jobsByMessageId[e.Id]), this.opt)
228
+
224
229
  // TODO Sentry
225
230
  }
226
231
  }
@@ -104,7 +104,7 @@ export class QueueManager {
104
104
  .filter(({ qname, qrl }) => {
105
105
  const isFifo = qname.endsWith('.fifo')
106
106
  const isDead = isFifo ? qname.endsWith('_dead.fifo') : qname.endsWith('_dead')
107
- return !isDead
107
+ return this.opt.includeDead ? true : !isDead
108
108
  })
109
109
  // then icehouse
110
110
  .filter(({ qname, qrl }) => !this.keepInIcehouse(qrl, now))
package/src/worker.js CHANGED
@@ -13,6 +13,7 @@ import treeKill from 'tree-kill'
13
13
  import chalk from 'chalk'
14
14
  import Debug from 'debug'
15
15
 
16
+ import { dedupSuccessfullyProcessed } from './dedup.js'
16
17
  import { normalizeQueueName, getQnameUrlPairs } from './qrlCache.js'
17
18
  import { getOptionsWithDefaults } from './defaults.js'
18
19
  import { cheapIdleCheck } from './idleQueues.js'
@@ -129,10 +130,15 @@ export async function executeJob (job, qname, qrl, opt) {
129
130
  QueueUrl: qrl,
130
131
  ReceiptHandle: job.ReceiptHandle
131
132
  }))
133
+
132
134
  if (opt.verbose) {
133
135
  console.error(chalk.blue(' done'))
134
136
  console.error()
135
137
  }
138
+
139
+ // Let dedup system know we processed it
140
+ await dedupSuccessfullyProcessed(job, opt)
141
+
136
142
  return { noJobs: 0, jobsSucceeded: 1, jobsFailed: 0 }
137
143
  } catch (err) {
138
144
  // Fail path for job execution