qdone 2.2.0 → 2.2.2

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.
@@ -94,8 +94,10 @@ function getOptionsWithDefaults(options) {
94
94
  // For API invocations don't force caller to supply default options
95
95
  if (!options)
96
96
  options = {};
97
- // Activate DLQ if any option is set
98
- const dlq = options.dlq || !!(options['dlq-suffix'] || options['dlq-after'] || options['dlq-name'] || options.dlqSuffix || options.dlqAfter || options.dlqName);
97
+ // Activate DLQ if any sub-option is set. Use ?? so that undefined (not
98
+ // passed) falls through to the default, while explicit false is preserved.
99
+ const hasDlqSubOption = !!(options['dlq-suffix'] || options['dlq-after'] || options['dlq-name'] || options.dlqSuffix || options.dlqAfter || options.dlqName);
100
+ const dlq = options.dlq ?? (hasDlqSubOption || undefined);
99
101
  const opt = {
100
102
  // Shared
101
103
  prefix: options.prefix === '' ? options.prefix : (options.prefix || process.env.QDONE_PREFIX || exports.defaults.prefix),
@@ -124,7 +126,7 @@ function getOptionsWithDefaults(options) {
124
126
  delay: options.delay || process.env.QDONE_DELAY || exports.defaults.delay,
125
127
  sendRetries: options.sendRetries || options['send-retries'] || process.env.QDONE_SEND_RETRIES || exports.defaults.sendRetries,
126
128
  failDelay: options.failDelay || options['fail-delay'] || process.env.QDONE_FAIL_DELAY || exports.defaults.failDelay,
127
- dlq: dlq === false ? false : exports.defaults.dlq,
129
+ dlq: dlq ?? exports.defaults.dlq,
128
130
  dlqSuffix: options.dlqSuffix || options['dlq-suffix'] || process.env.QDONE_DLQ_SUFFIX || exports.defaults.dlqSuffix,
129
131
  dlqAfter: options.dlqAfter || options['dlq-after'] || process.env.QDONE_DLQ_AFTER || exports.defaults.dlqAfter,
130
132
  tags: options.tags || undefined,
@@ -8,9 +8,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.monitor = monitor;
10
10
  exports.interpretWildcard = interpretWildcard;
11
+ exports.getQueueAge = getQueueAge;
11
12
  exports.getAggregateData = getAggregateData;
12
13
  const sqs_js_1 = require("./sqs.js");
13
14
  const cloudWatch_js_1 = require("./cloudWatch.js");
15
+ const client_cloudwatch_1 = require("@aws-sdk/client-cloudwatch");
14
16
  const defaults_js_1 = require("./defaults.js");
15
17
  const qrlCache_js_1 = require("./qrlCache.js");
16
18
  const debug_1 = __importDefault(require("debug"));
@@ -45,16 +47,45 @@ function interpretWildcard(queueName) {
45
47
  // debug({ prefix, suffix, safeSuffix, suffixRegex })
46
48
  return { prefix, suffix, safeSuffix, suffixRegex };
47
49
  }
50
+ /**
51
+ * Gets ApproximateAgeOfOldestMessage for a single queue from CloudWatch.
52
+ * This metric is not available via the SQS GetQueueAttributes API.
53
+ */
54
+ async function getQueueAge(queueName) {
55
+ const now = new Date();
56
+ const params = {
57
+ StartTime: new Date(now.getTime() - 1000 * 60 * 5),
58
+ EndTime: now,
59
+ MetricName: 'ApproximateAgeOfOldestMessage',
60
+ Namespace: 'AWS/SQS',
61
+ Period: 300,
62
+ Dimensions: [{ Name: 'QueueName', Value: queueName }],
63
+ Statistics: ['Maximum']
64
+ };
65
+ const client = (0, cloudWatch_js_1.getCloudWatchClient)();
66
+ const cmd = new client_cloudwatch_1.GetMetricStatisticsCommand(params);
67
+ try {
68
+ const data = await client.send(cmd);
69
+ debug('getQueueAge', queueName, data);
70
+ if (!data.Datapoints || data.Datapoints.length === 0)
71
+ return 0;
72
+ return Math.max(...data.Datapoints.map(d => d.Maximum));
73
+ }
74
+ catch (e) {
75
+ debug('getQueueAge error', queueName, e);
76
+ return 0;
77
+ }
78
+ }
48
79
  /**
49
80
  * Aggregates inmportant attributes across queues and reports a summary.
50
- * Attributes:
81
+ * Attributes (from SQS GetQueueAttributes):
51
82
  * - ApproximateNumberOfMessages: Sum
52
83
  * - ApproximateNumberOfMessagesDelayed: Sum
53
84
  * - ApproximateNumberOfMessagesNotVisible: Sum
85
+ * Metrics (from CloudWatch):
54
86
  * - ApproximateAgeOfOldestMessage: Max
55
87
  */
56
88
  async function getAggregateData(queueName) {
57
- const maxAttributes = new Set(['ApproximateAgeOfOldestMessage']);
58
89
  const { prefix, suffixRegex } = interpretWildcard(queueName);
59
90
  const qrls = await (0, sqs_js_1.getMatchingQueues)(prefix, suffixRegex);
60
91
  // debug({ qrls })
@@ -68,15 +99,14 @@ async function getAggregateData(queueName) {
68
99
  const newAtrribute = parseInt(result.Attributes[key], 10);
69
100
  if (newAtrribute > 0) {
70
101
  total.contributingQueueNames.add(queue);
71
- if (maxAttributes.has(key)) {
72
- total[key] = Math.max(total[key] || 0, newAtrribute);
73
- }
74
- else {
75
- total[key] = (total[key] || 0) + newAtrribute;
76
- }
102
+ total[key] = (total[key] || 0) + newAtrribute;
77
103
  }
78
104
  }
79
105
  }
106
+ // Fetch ApproximateAgeOfOldestMessage from CloudWatch (not available via SQS API)
107
+ // Only query queues with messages to minimize CloudWatch API costs
108
+ const ageResults = await Promise.all([...total.contributingQueueNames].map(queue => getQueueAge(queue)));
109
+ total.ApproximateAgeOfOldestMessage = Math.max(0, ...ageResults);
80
110
  // debug({ total })
81
111
  // convert set to array
82
112
  total.contributingQueueNames = [...total.contributingQueueNames.values()];
@@ -61,8 +61,7 @@ async function getQueueAttributes(qrls) {
61
61
  AttributeNames: [
62
62
  'ApproximateNumberOfMessages',
63
63
  'ApproximateNumberOfMessagesNotVisible',
64
- 'ApproximateNumberOfMessagesDelayed',
65
- 'ApproximateAgeOfOldestMessage'
64
+ 'ApproximateNumberOfMessagesDelayed'
66
65
  ]
67
66
  };
68
67
  const command = new client_sqs_1.GetQueueAttributesCommand(input);
@@ -84,8 +83,7 @@ async function getQueueAttributes(qrls) {
84
83
  Attributes: {
85
84
  ApproximateNumberOfMessages: '0',
86
85
  ApproximateNumberOfMessagesNotVisible: '0',
87
- ApproximateNumberOfMessagesDelayed: '0',
88
- ApproximateAgeOfOldestMessage: '0'
86
+ ApproximateNumberOfMessagesDelayed: '0'
89
87
  }
90
88
  };
91
89
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qdone",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
4
4
  "description": "A distributed scheduler for SQS",
5
5
  "type": "module",
6
6
  "main": "./index.js",
package/src/defaults.js CHANGED
@@ -90,8 +90,10 @@ export function getOptionsWithDefaults (options) {
90
90
  // For API invocations don't force caller to supply default options
91
91
  if (!options) options = {}
92
92
 
93
- // Activate DLQ if any option is set
94
- const dlq = options.dlq || !!(options['dlq-suffix'] || options['dlq-after'] || options['dlq-name'] || options.dlqSuffix || options.dlqAfter || options.dlqName)
93
+ // Activate DLQ if any sub-option is set. Use ?? so that undefined (not
94
+ // passed) falls through to the default, while explicit false is preserved.
95
+ const hasDlqSubOption = !!(options['dlq-suffix'] || options['dlq-after'] || options['dlq-name'] || options.dlqSuffix || options.dlqAfter || options.dlqName)
96
+ const dlq = options.dlq ?? (hasDlqSubOption || undefined)
95
97
 
96
98
  const opt = {
97
99
  // Shared
@@ -123,7 +125,7 @@ export function getOptionsWithDefaults (options) {
123
125
  delay: options.delay || process.env.QDONE_DELAY || defaults.delay,
124
126
  sendRetries: options.sendRetries || options['send-retries'] || process.env.QDONE_SEND_RETRIES || defaults.sendRetries,
125
127
  failDelay: options.failDelay || options['fail-delay'] || process.env.QDONE_FAIL_DELAY || defaults.failDelay,
126
- dlq: dlq === false ? false : defaults.dlq,
128
+ dlq: dlq ?? defaults.dlq,
127
129
  dlqSuffix: options.dlqSuffix || options['dlq-suffix'] || process.env.QDONE_DLQ_SUFFIX || defaults.dlqSuffix,
128
130
  dlqAfter: options.dlqAfter || options['dlq-after'] || process.env.QDONE_DLQ_AFTER || defaults.dlqAfter,
129
131
  tags: options.tags || undefined,
package/src/monitor.js CHANGED
@@ -3,7 +3,8 @@
3
3
  */
4
4
 
5
5
  import { getMatchingQueues, getQueueAttributes } from './sqs.js'
6
- import { putAggregateData } from './cloudWatch.js'
6
+ import { putAggregateData, getCloudWatchClient } from './cloudWatch.js'
7
+ import { GetMetricStatisticsCommand } from '@aws-sdk/client-cloudwatch'
7
8
  import { getOptionsWithDefaults } from './defaults.js'
8
9
  import { normalizeQueueName } from './qrlCache.js'
9
10
  import Debug from 'debug'
@@ -38,16 +39,44 @@ export function interpretWildcard (queueName) {
38
39
  return { prefix, suffix, safeSuffix, suffixRegex }
39
40
  }
40
41
 
42
+ /**
43
+ * Gets ApproximateAgeOfOldestMessage for a single queue from CloudWatch.
44
+ * This metric is not available via the SQS GetQueueAttributes API.
45
+ */
46
+ export async function getQueueAge (queueName) {
47
+ const now = new Date()
48
+ const params = {
49
+ StartTime: new Date(now.getTime() - 1000 * 60 * 5),
50
+ EndTime: now,
51
+ MetricName: 'ApproximateAgeOfOldestMessage',
52
+ Namespace: 'AWS/SQS',
53
+ Period: 300,
54
+ Dimensions: [{ Name: 'QueueName', Value: queueName }],
55
+ Statistics: ['Maximum']
56
+ }
57
+ const client = getCloudWatchClient()
58
+ const cmd = new GetMetricStatisticsCommand(params)
59
+ try {
60
+ const data = await client.send(cmd)
61
+ debug('getQueueAge', queueName, data)
62
+ if (!data.Datapoints || data.Datapoints.length === 0) return 0
63
+ return Math.max(...data.Datapoints.map(d => d.Maximum))
64
+ } catch (e) {
65
+ debug('getQueueAge error', queueName, e)
66
+ return 0
67
+ }
68
+ }
69
+
41
70
  /**
42
71
  * Aggregates inmportant attributes across queues and reports a summary.
43
- * Attributes:
72
+ * Attributes (from SQS GetQueueAttributes):
44
73
  * - ApproximateNumberOfMessages: Sum
45
74
  * - ApproximateNumberOfMessagesDelayed: Sum
46
75
  * - ApproximateNumberOfMessagesNotVisible: Sum
76
+ * Metrics (from CloudWatch):
47
77
  * - ApproximateAgeOfOldestMessage: Max
48
78
  */
49
79
  export async function getAggregateData (queueName) {
50
- const maxAttributes = new Set(['ApproximateAgeOfOldestMessage'])
51
80
  const { prefix, suffixRegex } = interpretWildcard(queueName)
52
81
  const qrls = await getMatchingQueues(prefix, suffixRegex)
53
82
  // debug({ qrls })
@@ -61,14 +90,18 @@ export async function getAggregateData (queueName) {
61
90
  const newAtrribute = parseInt(result.Attributes[key], 10)
62
91
  if (newAtrribute > 0) {
63
92
  total.contributingQueueNames.add(queue)
64
- if (maxAttributes.has(key)) {
65
- total[key] = Math.max(total[key] || 0, newAtrribute)
66
- } else {
67
- total[key] = (total[key] || 0) + newAtrribute
68
- }
93
+ total[key] = (total[key] || 0) + newAtrribute
69
94
  }
70
95
  }
71
96
  }
97
+
98
+ // Fetch ApproximateAgeOfOldestMessage from CloudWatch (not available via SQS API)
99
+ // Only query queues with messages to minimize CloudWatch API costs
100
+ const ageResults = await Promise.all(
101
+ [...total.contributingQueueNames].map(queue => getQueueAge(queue))
102
+ )
103
+ total.ApproximateAgeOfOldestMessage = Math.max(0, ...ageResults)
104
+
72
105
  // debug({ total })
73
106
  // convert set to array
74
107
  total.contributingQueueNames = [...total.contributingQueueNames.values()]
package/src/sqs.js CHANGED
@@ -55,8 +55,7 @@ export async function getQueueAttributes (qrls) {
55
55
  AttributeNames: [
56
56
  'ApproximateNumberOfMessages',
57
57
  'ApproximateNumberOfMessagesNotVisible',
58
- 'ApproximateNumberOfMessagesDelayed',
59
- 'ApproximateAgeOfOldestMessage'
58
+ 'ApproximateNumberOfMessagesDelayed'
60
59
  ]
61
60
  }
62
61
  const command = new GetQueueAttributesCommand(input)
@@ -77,8 +76,7 @@ export async function getQueueAttributes (qrls) {
77
76
  Attributes: {
78
77
  ApproximateNumberOfMessages: '0',
79
78
  ApproximateNumberOfMessagesNotVisible: '0',
80
- ApproximateNumberOfMessagesDelayed: '0',
81
- ApproximateAgeOfOldestMessage: '0'
79
+ ApproximateNumberOfMessagesDelayed: '0'
82
80
  }
83
81
  }
84
82
  }