qdone 2.1.0 → 2.1.1

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.
@@ -0,0 +1,333 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.idleQueues = exports.stripSuffixes = exports.processQueueSet = exports.deleteQueue = exports.checkIdle = exports.getMetric = exports.cheapIdleCheck = exports._cheapIdleCheck = exports.attributeNames = void 0;
7
+ /**
8
+ * Implementation of checks and caching of checks to determine if queues are idle.
9
+ */
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const sqs_js_1 = require("./sqs.js");
12
+ const cloudWatch_js_1 = require("./cloudWatch.js");
13
+ const defaults_js_1 = require("./defaults.js");
14
+ const client_sqs_1 = require("@aws-sdk/client-sqs");
15
+ const client_cloudwatch_1 = require("@aws-sdk/client-cloudwatch");
16
+ const qrlCache_js_1 = require("./qrlCache.js");
17
+ const cache_js_1 = require("./cache.js");
18
+ // const AWS = require('aws-sdk')
19
+ const debug_1 = __importDefault(require("debug"));
20
+ const debug = (0, debug_1.default)('qdone:idleQueues');
21
+ // Queue attributes we check to determine idle
22
+ exports.attributeNames = [
23
+ 'ApproximateNumberOfMessages',
24
+ 'ApproximateNumberOfMessagesNotVisible',
25
+ 'ApproximateNumberOfMessagesDelayed'
26
+ ];
27
+ // CloudWatch metrics we check to determine idle
28
+ const metricNames = [
29
+ 'NumberOfMessagesSent',
30
+ 'NumberOfMessagesReceived',
31
+ 'NumberOfMessagesDeleted',
32
+ 'ApproximateNumberOfMessagesVisible',
33
+ 'ApproximateNumberOfMessagesNotVisible',
34
+ 'ApproximateNumberOfMessagesDelayed',
35
+ // 'NumberOfEmptyReceives',
36
+ 'ApproximateAgeOfOldestMessage'
37
+ ];
38
+ /**
39
+ * Actual SQS call, used in conjunction with cache.
40
+ */
41
+ async function _cheapIdleCheck(qname, qrl, opt) {
42
+ debug('_cheapIdleCheck', qname, qrl);
43
+ try {
44
+ const client = (0, sqs_js_1.getSQSClient)();
45
+ const cmd = new client_sqs_1.GetQueueAttributesCommand({ AttributeNames: exports.attributeNames, QueueUrl: qrl });
46
+ const data = await client.send(cmd);
47
+ debug('data', data);
48
+ const result = data.Attributes;
49
+ result.queue = qname.slice(opt.prefix.length);
50
+ // We are idle if all the messages attributes are zero
51
+ result.idle = exports.attributeNames.filter(k => result[k] === '0').length === exports.attributeNames.length;
52
+ result.exists = true;
53
+ debug({ result, SQS: 1 });
54
+ return { result, SQS: 1 };
55
+ }
56
+ catch (e) {
57
+ debug({ _cheapIdleCheck: e });
58
+ if (e instanceof client_sqs_1.QueueDoesNotExist) {
59
+ return { result: { idle: undefined, exists: false }, SQS: 1 };
60
+ }
61
+ else {
62
+ throw e;
63
+ }
64
+ }
65
+ }
66
+ exports._cheapIdleCheck = _cheapIdleCheck;
67
+ /**
68
+ * Gets queue attributes from the SQS api and assesses whether queue is idle
69
+ * at this immediate moment.
70
+ */
71
+ async function cheapIdleCheck(qname, qrl, opt) {
72
+ debug('cheapIdleCheck', qname, qrl);
73
+ // Just call the API if we don't have a cache
74
+ if (!opt.cacheUri)
75
+ return _cheapIdleCheck(qname, qrl, opt);
76
+ // Otherwise check cache
77
+ const key = 'cheap-idle-check:' + qrl;
78
+ const cacheResult = await (0, cache_js_1.getCache)(key, opt);
79
+ debug({ cacheResult });
80
+ if (cacheResult) {
81
+ debug({ action: 'return resolved' });
82
+ return { result: cacheResult, SQS: 0 };
83
+ }
84
+ else {
85
+ // Cache miss, make call
86
+ debug({ action: 'do real check' });
87
+ const { result, SQS } = await _cheapIdleCheck(qname, qrl, opt);
88
+ debug({ action: 'setCache', key, result });
89
+ const ok = await (0, cache_js_1.setCache)(key, result, opt);
90
+ debug({ action: 'return result of set cache', ok });
91
+ return { result, SQS };
92
+ }
93
+ }
94
+ exports.cheapIdleCheck = cheapIdleCheck;
95
+ /**
96
+ * Gets a single metric from the CloudWatch api.
97
+ */
98
+ async function getMetric(qname, qrl, metricName, opt) {
99
+ debug('getMetric', qname, qrl, metricName);
100
+ const now = new Date();
101
+ const params = {
102
+ StartTime: new Date(now.getTime() - 1000 * 60 * opt.idleFor),
103
+ EndTime: now,
104
+ MetricName: metricName,
105
+ Namespace: 'AWS/SQS',
106
+ Period: 3600,
107
+ Dimensions: [{ Name: 'QueueName', Value: qname }],
108
+ Statistics: ['Sum']
109
+ // Unit: ['']
110
+ };
111
+ const client = (0, cloudWatch_js_1.getCloudWatchClient)();
112
+ const cmd = new client_cloudwatch_1.GetMetricStatisticsCommand(params);
113
+ const data = await client.send(cmd);
114
+ debug('getMetric data', data);
115
+ return {
116
+ [metricName]: data.Datapoints.map(d => d.Sum).reduce((a, b) => a + b, 0)
117
+ };
118
+ }
119
+ exports.getMetric = getMetric;
120
+ /**
121
+ * Checks if a single queue is idle. First queries the cheap ($0.40/1M) SQS API and
122
+ * continues to the expensive ($10/1M) CloudWatch API, checking to make sure there
123
+ * were no messages in the queue in the specified time period. Only if all relevant
124
+ * metrics show no messages, is it ok to delete the queue.
125
+ *
126
+ * Realistically, the number of CloudWatch calls will depend on usage patterns, but
127
+ * I see a ~70% reduction in calls by checking metrics in the given order.
128
+ * We could randomize the order, but for my test use case, it's always cheaper
129
+ * to check NumberOfMessagesSent first, and is the primary indicator of use.
130
+ */
131
+ async function checkIdle(qname, qrl, opt) {
132
+ // Do the cheap check first to make sure there is no data in flight at the moment
133
+ debug('checkIdle', qname, qrl);
134
+ const { result: cheapResult, SQS } = await cheapIdleCheck(qname, qrl, opt);
135
+ debug('cheapResult', cheapResult);
136
+ // Short circuit further calls if cheap result is conclusive
137
+ if (cheapResult.idle === false || cheapResult.exists === false) {
138
+ return {
139
+ queue: qname.slice(opt.prefix.length),
140
+ cheap: { SQS, result: cheapResult },
141
+ idle: cheapResult.idle,
142
+ exists: cheapResult.exists,
143
+ apiCalls: { SQS, CloudWatch: 0 }
144
+ };
145
+ }
146
+ // If we get here, there's nothing in the queue at the moment,
147
+ // so we have to check metrics one at a time
148
+ const apiCalls = { SQS, CloudWatch: 0 };
149
+ const results = [];
150
+ let idle = true;
151
+ for (const metricName of metricNames) {
152
+ // Check metrics in order
153
+ const result = await getMetric(qname, qrl, metricName, opt);
154
+ results.push(result);
155
+ debug('getMetric result', result);
156
+ apiCalls.CloudWatch++;
157
+ // Recalculate idle
158
+ idle = result[metricName] === 0;
159
+ if (!idle)
160
+ break; // and stop checking metrics if we find evidence of activity
161
+ }
162
+ // Calculate stats
163
+ const stats = Object.assign({
164
+ queue: qname.slice(opt.prefix.length),
165
+ cheap: cheapResult,
166
+ apiCalls,
167
+ idle,
168
+ exists: true
169
+ }, ...results // merge in results from CloudWatch
170
+ );
171
+ debug('checkIdle stats', stats);
172
+ return stats;
173
+ }
174
+ exports.checkIdle = checkIdle;
175
+ /**
176
+ * Just deletes a queue.
177
+ */
178
+ async function deleteQueue(qname, qrl, opt) {
179
+ const cmd = new client_sqs_1.DeleteQueueCommand({ QueueUrl: qrl });
180
+ const result = await (0, sqs_js_1.getSQSClient)().send(cmd);
181
+ debug(result);
182
+ if (opt.verbose)
183
+ console.error(chalk_1.default.blue('Deleted ') + qname.slice(opt.prefix.length));
184
+ return {
185
+ deleted: true,
186
+ apiCalls: { SQS: 1, CloudWatch: 0 }
187
+ };
188
+ }
189
+ exports.deleteQueue = deleteQueue;
190
+ /**
191
+ * Processes a queue and its fail and delete queue, treating them as a unit.
192
+ */
193
+ async function processQueueSet(qname, qrl, opt) {
194
+ const isFifo = qname.endsWith('.fifo');
195
+ const normalizeOptions = Object.assign({}, opt, { fifo: isFifo });
196
+ // Generate DLQ name/url
197
+ const dqname = (0, qrlCache_js_1.normalizeDLQName)(qname, normalizeOptions);
198
+ const dqrl = (0, qrlCache_js_1.normalizeDLQName)(dqname, normalizeOptions);
199
+ // Generate fail queue name/url
200
+ const fqname = (0, qrlCache_js_1.normalizeFailQueueName)(qname, normalizeOptions);
201
+ const fqrl = (0, qrlCache_js_1.normalizeFailQueueName)(fqname, normalizeOptions);
202
+ debug({ qname, qrl, dqname, dqrl, fqname, fqrl });
203
+ // Idle check
204
+ const qresult = await checkIdle(qname, qrl, opt);
205
+ const fqresult = await checkIdle(fqname, fqrl, opt);
206
+ const dqresult = await checkIdle(dqname, dqrl, opt);
207
+ debug({ qresult, fqresult, dqresult });
208
+ // Start building return value
209
+ const result = Object.assign({
210
+ queue: qname,
211
+ idle: (qresult.idle &&
212
+ (!fqresult.exists || fqresult.idle) &&
213
+ (!fqresult.exists || dqresult.idle)),
214
+ apiCalls: {
215
+ SQS: qresult.apiCalls.SQS + fqresult.apiCalls.SQS + dqresult.apiCalls.SQS,
216
+ CloudWatch: qresult.apiCalls.CloudWatch + fqresult.apiCalls.CloudWatch + dqresult.apiCalls.CloudWatch
217
+ }
218
+ });
219
+ // Queue is idle
220
+ if (qresult.idle && opt.verbose)
221
+ console.error(chalk_1.default.blue('Queue ') + qname.slice(opt.prefix.length) + chalk_1.default.blue(' has been ') + 'idle' + chalk_1.default.blue(' for the last ') + opt.idleFor + chalk_1.default.blue(' minutes.'));
222
+ if (fqresult.idle && opt.verbose)
223
+ console.error(chalk_1.default.blue('Queue ') + fqname.slice(opt.prefix.length) + chalk_1.default.blue(' has been ') + 'idle' + chalk_1.default.blue(' for the last ') + opt.idleFor + chalk_1.default.blue(' minutes.'));
224
+ if (dqresult.idle && opt.verbose)
225
+ console.error(chalk_1.default.blue('Queue ') + dqname.slice(opt.prefix.length) + chalk_1.default.blue(' has been ') + 'idle' + chalk_1.default.blue(' for the last ') + opt.idleFor + chalk_1.default.blue(' minutes.'));
226
+ // Delete if all are idle
227
+ const canDelete = ((qresult.idle || qresult.exists === false) &&
228
+ (fqresult.idle || fqresult.exists === false) &&
229
+ (dqresult.idle || dqresult.exists === false));
230
+ debug({ canDelete });
231
+ if (opt.delete && canDelete) {
232
+ // Normal
233
+ const qdresult = await (async () => {
234
+ debug({ qresult });
235
+ try {
236
+ if (qresult.idle)
237
+ return deleteQueue(qname, qrl, opt);
238
+ }
239
+ catch (e) {
240
+ if (!(e instanceof client_sqs_1.QueueDoesNotExist))
241
+ throw e;
242
+ }
243
+ })();
244
+ if (qdresult) {
245
+ result.apiCalls.SQS += qdresult.apiCalls.SQS;
246
+ }
247
+ debug({ qdresult });
248
+ // Fail
249
+ const fqdresult = await (async () => {
250
+ debug({ fqresult });
251
+ try {
252
+ if (fqresult.idle)
253
+ return deleteQueue(fqname, fqrl, opt);
254
+ }
255
+ catch (e) {
256
+ if (!(e instanceof client_sqs_1.QueueDoesNotExist))
257
+ throw e;
258
+ }
259
+ })();
260
+ if (fqdresult) {
261
+ result.apiCalls.SQS += fqdresult.apiCalls.SQS;
262
+ }
263
+ debug({ fqdresult });
264
+ // Dead
265
+ const dqdresult = await (async () => {
266
+ debug({ dqresult });
267
+ try {
268
+ if (dqresult.idle)
269
+ return deleteQueue(dqname, dqrl, opt);
270
+ }
271
+ catch (e) {
272
+ if (!(e instanceof client_sqs_1.QueueDoesNotExist))
273
+ throw e;
274
+ }
275
+ })();
276
+ if (dqdresult) {
277
+ result.apiCalls.SQS += dqdresult.apiCalls.SQS;
278
+ }
279
+ debug({ dqdresult });
280
+ }
281
+ return result;
282
+ }
283
+ exports.processQueueSet = processQueueSet;
284
+ //
285
+ // Strips failed and dlq suffix from a queue name or URL
286
+ //
287
+ function stripSuffixes(queueName, opt) {
288
+ const suffixFinder = new RegExp(`(${opt.dlqSuffix}|${opt.failSuffix}){1}(|${qrlCache_js_1.fifoSuffix})$`);
289
+ return queueName.replace(suffixFinder, '$2');
290
+ }
291
+ exports.stripSuffixes = stripSuffixes;
292
+ //
293
+ // Resolve queues for listening loop listen
294
+ //
295
+ async function idleQueues(queues, options) {
296
+ const opt = (0, defaults_js_1.getOptionsWithDefaults)(options);
297
+ if (opt.verbose)
298
+ console.error(chalk_1.default.blue('Resolving queues: ') + queues.join(' '));
299
+ const qnames = queues.map(queue => opt.prefix + queue);
300
+ const entries = await (0, qrlCache_js_1.getQnameUrlPairs)(qnames, opt);
301
+ debug('getQnameUrlPairs.then');
302
+ if (opt.verbose) {
303
+ console.error(chalk_1.default.blue(' done'));
304
+ console.error();
305
+ }
306
+ // Filter out failed and dead queues, but if we have an orphaned fail or
307
+ // dead queue, keep the original parent queue name so that orphans can be
308
+ // deleted.
309
+ const queueNames = new Set();
310
+ const filteredEntries = entries.filter(entry => {
311
+ const stripped = stripSuffixes(entry.qname, opt);
312
+ if (queueNames.has(stripped))
313
+ return false;
314
+ queueNames.add(stripped);
315
+ entry.qname = stripped;
316
+ entry.qrl = stripSuffixes(entry.qrl, opt);
317
+ return true;
318
+ });
319
+ // But only if we have queues to remove
320
+ if (filteredEntries.length) {
321
+ if (opt.verbose) {
322
+ console.error(chalk_1.default.blue('Checking queues (in this order):'));
323
+ console.error(filteredEntries.map(e => ' ' + e.qname.slice(opt.prefix.length) + chalk_1.default.blue(' - ' + e.qrl)).join('\n'));
324
+ console.error();
325
+ }
326
+ // Check each queue in parallel
327
+ return Promise.all(filteredEntries.map(e => processQueueSet(e.qname, e.qrl, opt)));
328
+ }
329
+ // Otherwise, let caller know
330
+ return 'noQueues';
331
+ }
332
+ exports.idleQueues = idleQueues;
333
+ debug('loaded');
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ /**
3
+ * Multi-queue monitoring functionaliry
4
+ */
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.getAggregateData = exports.interpretWildcard = exports.monitor = void 0;
10
+ const sqs_js_1 = require("./sqs.js");
11
+ const cloudWatch_js_1 = require("./cloudWatch.js");
12
+ const defaults_js_1 = require("./defaults.js");
13
+ const qrlCache_js_1 = require("./qrlCache.js");
14
+ const debug_1 = __importDefault(require("debug"));
15
+ const debug = (0, debug_1.default)('qdone:monitor');
16
+ /**
17
+ * Splits a queue name with a single wildcard into prefix and suffix regex.
18
+ */
19
+ async function monitor(queue, save, options) {
20
+ if (queue.endsWith('.fifo'))
21
+ options.fifo = true;
22
+ const opt = (0, defaults_js_1.getOptionsWithDefaults)(options);
23
+ const queueName = (0, qrlCache_js_1.normalizeQueueName)(queue, opt);
24
+ debug({ options, opt, queue, queueName });
25
+ const data = await getAggregateData(queueName);
26
+ console.log(data);
27
+ if (save) {
28
+ if (opt.verbose)
29
+ process.stderr.write('Saving to CloudWatch...');
30
+ await (0, cloudWatch_js_1.putAggregateData)(data);
31
+ if (opt.verbose)
32
+ process.stderr.write('done\n');
33
+ }
34
+ }
35
+ exports.monitor = monitor;
36
+ /**
37
+ * Splits a queue name with a single wildcard into prefix and suffix regex.
38
+ */
39
+ function interpretWildcard(queueName) {
40
+ const [prefix, suffix] = queueName.split('*');
41
+ // Strip anything that could cause backreferences
42
+ const safeSuffix = (suffix || '').replace(/[^a-zA-Z0-9_.]+/g, '').replace(/\./g, '\\.');
43
+ const suffixRegex = new RegExp(`${safeSuffix}$`);
44
+ // debug({ prefix, suffix, safeSuffix, suffixRegex })
45
+ return { prefix, suffix, safeSuffix, suffixRegex };
46
+ }
47
+ exports.interpretWildcard = interpretWildcard;
48
+ /**
49
+ * Aggregates inmportant attributes across queues and reports a summary.
50
+ * Attributes:
51
+ * - ApproximateNumberOfMessages: Sum
52
+ * - ApproximateNumberOfMessagesDelayed: Sum
53
+ * - ApproximateNumberOfMessagesNotVisible: Sum
54
+ */
55
+ async function getAggregateData(queueName) {
56
+ const { prefix, suffixRegex } = interpretWildcard(queueName);
57
+ const qrls = await (0, sqs_js_1.getMatchingQueues)(prefix, suffixRegex);
58
+ // debug({ qrls })
59
+ const data = await (0, sqs_js_1.getQueueAttributes)(qrls);
60
+ // debug({ data })
61
+ const total = { totalQueues: 0, contributingQueueNames: new Set() };
62
+ for (const { queue, result } of data) {
63
+ // debug({ row })
64
+ total.totalQueues++;
65
+ for (const key in result.Attributes) {
66
+ const newAtrribute = parseInt(result.Attributes[key], 10);
67
+ if (newAtrribute > 0) {
68
+ total.contributingQueueNames.add(queue);
69
+ total[key] = (total[key] || 0) + newAtrribute;
70
+ }
71
+ }
72
+ }
73
+ // debug({ total })
74
+ // convert set to array
75
+ total.contributingQueueNames = [...total.contributingQueueNames.values()];
76
+ total.queueName = queueName;
77
+ return total;
78
+ }
79
+ exports.getAggregateData = getAggregateData;
80
+ debug('loaded');
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ /**
3
+ * A cache for maintaining queue urls. Avoids having to call the GetQueueUrl
4
+ * api too often.
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.getQnameUrlPairs = exports.getMatchingQueues = exports.ingestQRLs = exports.qrlCacheInvalidate = exports.qrlCacheSet = exports.qrlCacheGet = exports.qrlCacheClear = exports.normalizeDLQName = exports.normalizeFailQueueName = exports.normalizeQueueName = exports.fifoSuffix = void 0;
11
+ const url_1 = require("url");
12
+ const path_1 = require("path");
13
+ const client_sqs_1 = require("@aws-sdk/client-sqs");
14
+ const sqs_js_1 = require("./sqs.js");
15
+ const debug_1 = __importDefault(require("debug"));
16
+ const debug = (0, debug_1.default)('qdone:qrlCache');
17
+ // The actual cache is shared across callers.
18
+ const qcache = new Map();
19
+ //
20
+ // Normalizes a queue name to end with .fifo if opt.fifo is set
21
+ //
22
+ exports.fifoSuffix = '.fifo';
23
+ function normalizeQueueName(qname, opt) {
24
+ const hasFifo = qname.endsWith(exports.fifoSuffix);
25
+ const needsFifo = opt.fifo && qname.slice(-1) !== '*';
26
+ const needsPrefix = !qname.startsWith(opt.prefix);
27
+ const base = hasFifo ? qname.slice(0, -exports.fifoSuffix.length) : qname;
28
+ return (needsPrefix ? opt.prefix : '') + base + (needsFifo ? exports.fifoSuffix : '');
29
+ }
30
+ exports.normalizeQueueName = normalizeQueueName;
31
+ //
32
+ // Normalizes fail queue name, appending both --fail-suffix and .fifo depending on opt
33
+ //
34
+ function normalizeFailQueueName(fqname, opt) {
35
+ const newopt = Object.assign({}, opt, { fifo: false });
36
+ const base = normalizeQueueName(fqname, newopt); // strip fifo
37
+ debug({ fqname, base, opt });
38
+ const needsFail = !base.endsWith(opt.failSuffix);
39
+ return base + (needsFail ? opt.failSuffix : '') + (opt.fifo ? exports.fifoSuffix : '');
40
+ }
41
+ exports.normalizeFailQueueName = normalizeFailQueueName;
42
+ //
43
+ // Normalizes dlq queue name, appending both --dlq-suffix and .fifo depending on opt
44
+ //
45
+ function normalizeDLQName(dqname, opt) {
46
+ const newopt = Object.assign({}, opt, { fifo: false });
47
+ const base = normalizeQueueName(dqname, newopt); // strip fifo
48
+ const needsDead = !base.endsWith(opt.dlqSuffix);
49
+ return base + (needsDead ? opt.dlqSuffix : '') + (opt.fifo ? exports.fifoSuffix : '');
50
+ }
51
+ exports.normalizeDLQName = normalizeDLQName;
52
+ //
53
+ // Clear cache
54
+ //
55
+ function qrlCacheClear() {
56
+ qcache.clear();
57
+ }
58
+ exports.qrlCacheClear = qrlCacheClear;
59
+ //
60
+ // Get QRL (Queue URL)
61
+ // Returns a promise for the queue name
62
+ //
63
+ async function qrlCacheGet(qname) {
64
+ debug('get', qname, qcache);
65
+ if (qcache.has(qname))
66
+ return qcache.get(qname);
67
+ const client = (0, sqs_js_1.getSQSClient)();
68
+ const input = { QueueName: qname };
69
+ const cmd = new client_sqs_1.GetQueueUrlCommand(input);
70
+ // debug({ cmd })
71
+ const result = await client.send(cmd);
72
+ // debug('result', result)
73
+ if (!result) {
74
+ qrlCacheInvalidate(qname);
75
+ throw new client_sqs_1.QueueDoesNotExist(qname);
76
+ }
77
+ const { QueueUrl: qrl } = result;
78
+ // debug('getQueueUrl returned', data)
79
+ qcache.set(qname, qrl);
80
+ // debug('qcache', Object.keys(qcache), 'get', qname, ' => ', qcache[qname])
81
+ return qrl;
82
+ }
83
+ exports.qrlCacheGet = qrlCacheGet;
84
+ //
85
+ // Set QRL (Queue URL)
86
+ // Immediately updates the cache
87
+ //
88
+ function qrlCacheSet(qname, qrl) {
89
+ if (qrl)
90
+ qcache.set(qname, qrl);
91
+ // debug('qcache', Object.keys(qcache), 'set', qname, ' => ', qcache[qname])
92
+ }
93
+ exports.qrlCacheSet = qrlCacheSet;
94
+ //
95
+ // Invalidate cache for given qname
96
+ //
97
+ function qrlCacheInvalidate(qname) {
98
+ // debug('qcache', Object.keys(qcache), 'delete', qname, ' (was ', qcache[qname], ')')
99
+ qcache.delete(qname);
100
+ }
101
+ exports.qrlCacheInvalidate = qrlCacheInvalidate;
102
+ //
103
+ // Ingets multiple QRLs
104
+ // Extracts queue names from an array of QRLs and immediately updates the cache.
105
+ //
106
+ function ingestQRLs(qrls) {
107
+ const pairs = [];
108
+ debug('ingestQRLs', qrls);
109
+ for (const qrl of qrls) {
110
+ const qname = (0, path_1.basename)((new url_1.URL(qrl)).pathname);
111
+ qrlCacheSet(qname, qrl);
112
+ pairs.push({ qname, qrl });
113
+ // debug('qcache', Object.keys(qcache), 'ingestQRLs', qname, ' => ', qcache[qname])
114
+ }
115
+ return pairs;
116
+ }
117
+ exports.ingestQRLs = ingestQRLs;
118
+ /**
119
+ * Returns qrls for queues matching the given prefix and regex.
120
+ */
121
+ async function getMatchingQueues(prefix, nextToken) {
122
+ debug('getMatchingQueues', prefix, nextToken);
123
+ const input = { QueueNamePrefix: prefix, MaxResults: 1000 };
124
+ if (nextToken)
125
+ input.NextToken = nextToken;
126
+ const client = (0, sqs_js_1.getSQSClient)();
127
+ const command = new client_sqs_1.ListQueuesCommand(input);
128
+ const result = await client.send(command);
129
+ debug({ result });
130
+ const { QueueUrls: qrls, NextToken: keepGoing } = result || {};
131
+ debug({ qrls, keepGoing });
132
+ if (keepGoing)
133
+ (qrls || []).push(...await getMatchingQueues(prefix, keepGoing));
134
+ return qrls || [];
135
+ }
136
+ exports.getMatchingQueues = getMatchingQueues;
137
+ //
138
+ // Resolves into a flattened aray of {qname: ..., qrl: ...} objects.
139
+ //
140
+ async function getQnameUrlPairs(qnames, opt) {
141
+ const promises = qnames.map(async function getQueueUrlOrUrls(qname) {
142
+ if (qname.slice(-1) === '*') {
143
+ // Wildcard queues support
144
+ const prefix = qname.slice(0, -1);
145
+ const queues = await getMatchingQueues(prefix);
146
+ debug('listQueues return', queues);
147
+ if (opt.fifo) {
148
+ // Remove non-fifo queues
149
+ const filteredQueues = queues.filter(url => url.slice(-exports.fifoSuffix.length) === exports.fifoSuffix);
150
+ return ingestQRLs(filteredQueues);
151
+ }
152
+ else {
153
+ return ingestQRLs(queues);
154
+ }
155
+ }
156
+ else {
157
+ // Normal, non wildcard queue support
158
+ try {
159
+ return { qname, qrl: await qrlCacheGet(qname) };
160
+ }
161
+ catch (err) {
162
+ if (!(err instanceof client_sqs_1.QueueDoesNotExist))
163
+ throw err;
164
+ }
165
+ }
166
+ });
167
+ // Flatten nested results
168
+ const results = await Promise.all(promises);
169
+ return ([].concat.apply([], results)).filter(r => r);
170
+ }
171
+ exports.getQnameUrlPairs = getQnameUrlPairs;
172
+ debug('loaded');