qdone 2.2.2 → 2.2.4

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.
@@ -125,7 +125,7 @@ async function statMaintenance(opt) {
125
125
  const duplicateSet = opt.cachePrefix + 'dedup-stats:duplicateSet';
126
126
  const expirationSet = opt.cachePrefix + 'dedup-stats:expirationSet';
127
127
  const client = (0, cache_js_1.getCacheClient)(opt);
128
- const now = new Date().getTime();
128
+ const now = Math.floor(Date.now() / 1000);
129
129
  // Grab a batch of expired keys
130
130
  debug({ statMaintenance: { aboutToGo: true, expirationSet } });
131
131
  const expiredStats = await client.zrange(expirationSet, '-inf', now, 'BYSCORE');
@@ -150,7 +150,7 @@ async function dedupShouldEnqueue(message, opt) {
150
150
  const client = (0, cache_js_1.getCacheClient)(opt);
151
151
  const dedupId = message?.MessageAttributes?.QdoneDeduplicationId?.StringValue;
152
152
  const cacheKey = getCacheKey(dedupId, opt);
153
- const expireAt = new Date().getTime() + opt.dedupPeriod;
153
+ const expireAt = Math.floor(Date.now() / 1000) + opt.dedupPeriod;
154
154
  const copies = await client.incr(cacheKey);
155
155
  debug({ action: 'shouldEnqueue', cacheKey, copies });
156
156
  if (copies === 1) {
@@ -172,7 +172,7 @@ async function dedupShouldEnqueue(message, opt) {
172
172
  */
173
173
  async function dedupShouldEnqueueMulti(messages, opt) {
174
174
  debug({ dedupShouldEnqueueMulti: { messages, opt } });
175
- const expireAt = new Date().getTime() + opt.dedupPeriod;
175
+ const expireAt = Math.floor(Date.now() / 1000) + opt.dedupPeriod;
176
176
  // Increment all
177
177
  const incrPipeline = (0, cache_js_1.getCacheClient)(opt).pipeline();
178
178
  for (const message of messages) {
@@ -26,7 +26,7 @@ async function monitor(queue, save, options) {
26
26
  const opt = (0, defaults_js_1.getOptionsWithDefaults)(options);
27
27
  const queueName = (0, qrlCache_js_1.normalizeQueueName)(queue, opt);
28
28
  debug({ options, opt, queue, queueName });
29
- const data = await getAggregateData(queueName);
29
+ const data = await getAggregateData(queueName, opt);
30
30
  console.log(data);
31
31
  if (save) {
32
32
  if (opt.verbose)
@@ -85,7 +85,7 @@ async function getQueueAge(queueName) {
85
85
  * Metrics (from CloudWatch):
86
86
  * - ApproximateAgeOfOldestMessage: Max
87
87
  */
88
- async function getAggregateData(queueName) {
88
+ async function getAggregateData(queueName, opt) {
89
89
  const { prefix, suffixRegex } = interpretWildcard(queueName);
90
90
  const qrls = await (0, sqs_js_1.getMatchingQueues)(prefix, suffixRegex);
91
91
  // debug({ qrls })
@@ -105,7 +105,19 @@ async function getAggregateData(queueName) {
105
105
  }
106
106
  // Fetch ApproximateAgeOfOldestMessage from CloudWatch (not available via SQS API)
107
107
  // Only query queues with messages to minimize CloudWatch API costs
108
- const ageResults = await Promise.all([...total.contributingQueueNames].map(queue => getQueueAge(queue)));
108
+ // Filter out dead and failed queues for age calculation only — their messages
109
+ // age indefinitely by design, polluting the active age metric.
110
+ // But if the pattern itself targets dead/failed queues, don't filter them out.
111
+ const failSuffix = (opt && opt.failSuffix) || defaults_js_1.defaults.failSuffix;
112
+ const dlqSuffix = (opt && opt.dlqSuffix) || defaults_js_1.defaults.dlqSuffix;
113
+ const strippedPattern = queueName.replace(/\.fifo$/, '');
114
+ const patternTargetsDeadFailed = strippedPattern.endsWith(failSuffix) || strippedPattern.endsWith(dlqSuffix);
115
+ const esc = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
116
+ const deadFailedRegex = new RegExp(`(${esc(failSuffix)}|${esc(dlqSuffix)})(\\.fifo)?$`);
117
+ const activeQueueNames = patternTargetsDeadFailed
118
+ ? [...total.contributingQueueNames]
119
+ : [...total.contributingQueueNames].filter(q => !deadFailedRegex.test(q));
120
+ const ageResults = await Promise.all(activeQueueNames.map(queue => getQueueAge(queue)));
109
121
  total.ApproximateAgeOfOldestMessage = Math.max(0, ...ageResults);
110
122
  // debug({ total })
111
123
  // convert set to array
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qdone",
3
- "version": "2.2.2",
3
+ "version": "2.2.4",
4
4
  "description": "A distributed scheduler for SQS",
5
5
  "type": "module",
6
6
  "main": "./index.js",
package/src/cli.js CHANGED
@@ -197,10 +197,11 @@ export async function check (argv, testHook) {
197
197
  }
198
198
 
199
199
  const monitorOptionDefinitions = [
200
- { name: 'save', alias: 's', type: Boolean, description: 'Saves data to CloudWatch' }
200
+ { name: 'save', alias: 's', type: Boolean, description: 'Saves data to CloudWatch' },
201
+ { name: 'dlq-suffix', type: String, description: `Suffix to append to each queue to generate DLQ name [default: ${defaults.dlqSuffix}]` }
201
202
  ]
202
203
 
203
- export async function monitor (argv) {
204
+ export async function monitor (argv, testHook) {
204
205
  const optionDefinitions = [].concat(monitorOptionDefinitions, globalOptionDefinitions)
205
206
  const usageSections = [
206
207
  { content: 'usage: qdone monitor <queuePattern> ', raw: true },
@@ -234,7 +235,8 @@ export async function monitor (argv) {
234
235
 
235
236
  // Load module after AWS global load
236
237
  setupAWS(options)
237
- const { monitor } = await import('./monitor.js')
238
+ const { monitor: monitorOriginal } = await import('./monitor.js')
239
+ const monitor = testHook || monitorOriginal
238
240
  return monitor(queue, options.save, options)
239
241
  }
240
242
 
package/src/dedup.js CHANGED
@@ -112,7 +112,7 @@ export async function statMaintenance (opt) {
112
112
  const duplicateSet = opt.cachePrefix + 'dedup-stats:duplicateSet'
113
113
  const expirationSet = opt.cachePrefix + 'dedup-stats:expirationSet'
114
114
  const client = getCacheClient(opt)
115
- const now = new Date().getTime()
115
+ const now = Math.floor(Date.now() / 1000)
116
116
 
117
117
  // Grab a batch of expired keys
118
118
  debug({ statMaintenance: { aboutToGo: true, expirationSet } })
@@ -140,7 +140,7 @@ export async function dedupShouldEnqueue (message, opt) {
140
140
  const client = getCacheClient(opt)
141
141
  const dedupId = message?.MessageAttributes?.QdoneDeduplicationId?.StringValue
142
142
  const cacheKey = getCacheKey(dedupId, opt)
143
- const expireAt = new Date().getTime() + opt.dedupPeriod
143
+ const expireAt = Math.floor(Date.now() / 1000) + opt.dedupPeriod
144
144
  const copies = await client.incr(cacheKey)
145
145
  debug({ action: 'shouldEnqueue', cacheKey, copies })
146
146
  if (copies === 1) {
@@ -163,7 +163,7 @@ export async function dedupShouldEnqueue (message, opt) {
163
163
  */
164
164
  export async function dedupShouldEnqueueMulti (messages, opt) {
165
165
  debug({ dedupShouldEnqueueMulti: { messages, opt } })
166
- const expireAt = new Date().getTime() + opt.dedupPeriod
166
+ const expireAt = Math.floor(Date.now() / 1000) + opt.dedupPeriod
167
167
  // Increment all
168
168
  const incrPipeline = getCacheClient(opt).pipeline()
169
169
  for (const message of messages) {
package/src/monitor.js CHANGED
@@ -5,7 +5,7 @@
5
5
  import { getMatchingQueues, getQueueAttributes } from './sqs.js'
6
6
  import { putAggregateData, getCloudWatchClient } from './cloudWatch.js'
7
7
  import { GetMetricStatisticsCommand } from '@aws-sdk/client-cloudwatch'
8
- import { getOptionsWithDefaults } from './defaults.js'
8
+ import { getOptionsWithDefaults, defaults } from './defaults.js'
9
9
  import { normalizeQueueName } from './qrlCache.js'
10
10
  import Debug from 'debug'
11
11
  const debug = Debug('qdone:monitor')
@@ -18,7 +18,7 @@ export async function monitor (queue, save, options) {
18
18
  const opt = getOptionsWithDefaults(options)
19
19
  const queueName = normalizeQueueName(queue, opt)
20
20
  debug({ options, opt, queue, queueName })
21
- const data = await getAggregateData(queueName)
21
+ const data = await getAggregateData(queueName, opt)
22
22
  console.log(data)
23
23
  if (save) {
24
24
  if (opt.verbose) process.stderr.write('Saving to CloudWatch...')
@@ -76,7 +76,7 @@ export async function getQueueAge (queueName) {
76
76
  * Metrics (from CloudWatch):
77
77
  * - ApproximateAgeOfOldestMessage: Max
78
78
  */
79
- export async function getAggregateData (queueName) {
79
+ export async function getAggregateData (queueName, opt) {
80
80
  const { prefix, suffixRegex } = interpretWildcard(queueName)
81
81
  const qrls = await getMatchingQueues(prefix, suffixRegex)
82
82
  // debug({ qrls })
@@ -97,8 +97,21 @@ export async function getAggregateData (queueName) {
97
97
 
98
98
  // Fetch ApproximateAgeOfOldestMessage from CloudWatch (not available via SQS API)
99
99
  // Only query queues with messages to minimize CloudWatch API costs
100
+ // Filter out dead and failed queues for age calculation only — their messages
101
+ // age indefinitely by design, polluting the active age metric.
102
+ // But if the pattern itself targets dead/failed queues, don't filter them out.
103
+ const failSuffix = (opt && opt.failSuffix) || defaults.failSuffix
104
+ const dlqSuffix = (opt && opt.dlqSuffix) || defaults.dlqSuffix
105
+ const strippedPattern = queueName.replace(/\.fifo$/, '')
106
+ const patternTargetsDeadFailed = strippedPattern.endsWith(failSuffix) || strippedPattern.endsWith(dlqSuffix)
107
+ const esc = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
108
+ const deadFailedRegex = new RegExp(`(${esc(failSuffix)}|${esc(dlqSuffix)})(\\.fifo)?$`)
109
+ const activeQueueNames = patternTargetsDeadFailed
110
+ ? [...total.contributingQueueNames]
111
+ : [...total.contributingQueueNames].filter(q => !deadFailedRegex.test(q))
112
+
100
113
  const ageResults = await Promise.all(
101
- [...total.contributingQueueNames].map(queue => getQueueAge(queue))
114
+ activeQueueNames.map(queue => getQueueAge(queue))
102
115
  )
103
116
  total.ApproximateAgeOfOldestMessage = Math.max(0, ...ageResults)
104
117