qdone 1.7.0 → 2.0.0-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/qrlCache.js CHANGED
@@ -1,134 +1,161 @@
1
+ /**
2
+ * A cache for maintaining queue urls. Avoids having to call the GetQueueUrl
3
+ * api too often.
4
+ */
1
5
 
2
- const Q = require('q')
3
- const debug = require('debug')('qdone:qrlCache')
4
- const chalk = require('chalk')
5
- const url = require('url')
6
- const path = require('path')
7
- const AWS = require('aws-sdk')
6
+ import { URL } from 'url'
7
+ import { basename } from 'path'
8
+ import { ListQueuesCommand, GetQueueUrlCommand, QueueDoesNotExist } from '@aws-sdk/client-sqs'
9
+ import { getSQSClient } from './sqs.js'
10
+ import Debug from 'debug'
11
+ const debug = Debug('qdone:qrlCache')
8
12
 
9
- const qcache = {}
13
+ // The actual cache is shared across callers.
14
+ const qcache = new Map()
10
15
 
11
- function _get (qname) {
12
- return qcache[qname]
16
+ //
17
+ // Normalizes a queue name to end with .fifo if opt.fifo is set
18
+ //
19
+ export const fifoSuffix = '.fifo'
20
+ export function normalizeQueueName (qname, opt) {
21
+ const hasFifo = qname.endsWith(fifoSuffix)
22
+ const needsFifo = opt.fifo && qname.slice(-1) !== '*'
23
+ const needsPrefix = !qname.startsWith(opt.prefix)
24
+ const base = hasFifo ? qname.slice(0, -fifoSuffix.length) : qname
25
+ return (needsPrefix ? opt.prefix : '') + base + (needsFifo ? fifoSuffix : '')
13
26
  }
14
27
 
15
28
  //
16
- // Normalizes a queue name to end with .fifo if options.fifo is set
29
+ // Normalizes fail queue name, appending both --fail-suffix and .fifo depending on opt
17
30
  //
18
- const fifoSuffix = '.fifo'
19
- exports.fifoSuffix = fifoSuffix
20
- exports.normalizeQueueName = function normalizeQueueName (qname, options) {
21
- const sliced = qname.slice(0, -fifoSuffix.length)
22
- const suffix = qname.slice(-fifoSuffix.length)
23
- const base = suffix === fifoSuffix ? sliced : qname
24
- return base + (options.fifo && qname.slice(-1) !== '*' ? fifoSuffix : '')
31
+ export function normalizeFailQueueName (fqname, opt) {
32
+ const newopt = Object.assign({}, opt, { fifo: false })
33
+ const base = normalizeQueueName(fqname, newopt) // strip fifo
34
+ debug({ fqname, base, opt })
35
+ const needsFail = !base.endsWith(opt.failSuffix)
36
+ return base + (needsFail ? opt.failSuffix : '') + (opt.fifo ? fifoSuffix : '')
25
37
  }
26
38
 
27
39
  //
28
- // Normalizes fail queue name, appending both --fail-suffix and .fifo depending on options
40
+ // Normalizes dlq queue name, appending both --dlq-suffix and .fifo depending on opt
29
41
  //
30
- exports.normalizeFailQueueName = function normalizeFailQueueName (qname, options) {
31
- qname = exports.normalizeQueueName(qname, { fifo: false }) // strip .fifo if it is there
32
- const sliced = qname.slice(0, -options['fail-suffix'].length)
33
- const suffix = qname.slice(-options['fail-suffix'].length)
34
- const base = suffix === options['fail-suffix'] ? sliced : qname // strip --fail-suffix if it is there
35
- return (base + options['fail-suffix']) + (options.fifo ? fifoSuffix : '')
42
+ export function normalizeDLQName (dqname, opt) {
43
+ const newopt = Object.assign({}, opt, { fifo: false })
44
+ const base = normalizeQueueName(dqname, newopt) // strip fifo
45
+ const needsDead = !base.endsWith(opt.dlqSuffix)
46
+ return base + (needsDead ? opt.dlqSuffix : '') + (opt.fifo ? fifoSuffix : '')
36
47
  }
37
48
 
38
49
  //
39
50
  // Clear cache
40
51
  //
41
- exports.clear = function clear () {
42
- Object.keys(qcache).forEach(function (key) {
43
- delete qcache[key]
44
- })
52
+ export function qrlCacheClear () {
53
+ qcache.clear()
45
54
  }
46
55
 
47
56
  //
48
57
  // Get QRL (Queue URL)
49
58
  // Returns a promise for the queue name
50
59
  //
51
- exports.get = function get (qname) {
60
+ export async function qrlCacheGet (qname) {
52
61
  debug('get', qname, qcache)
53
- const sqs = new AWS.SQS()
54
- if (Object.prototype.hasOwnProperty.call(qcache, qname)) return Q.fcall(_get, qname)
55
- return sqs
56
- .getQueueUrl({ QueueName: qname })
57
- .promise()
58
- .then(function (data) {
59
- debug('getQueueUrl returned', data)
60
- qcache[qname] = data.QueueUrl
61
- debug('qcache', Object.keys(qcache), 'get', qname, ' => ', qcache[qname])
62
- return Q.fcall(_get, qname)
63
- })
62
+ if (qcache.has(qname)) return qcache.get(qname)
63
+ const client = getSQSClient()
64
+ const input = { QueueName: qname }
65
+ const cmd = new GetQueueUrlCommand(input)
66
+ // debug({ cmd })
67
+ const result = await client.send(cmd)
68
+ // debug('result', result)
69
+ // if (!result) throw new Error(`No such queue ${qname}`)
70
+ const { QueueUrl: qrl } = result
71
+ // debug('getQueueUrl returned', data)
72
+ qcache.set(qname, qrl)
73
+ // debug('qcache', Object.keys(qcache), 'get', qname, ' => ', qcache[qname])
74
+ return qrl
64
75
  }
65
76
 
66
77
  //
67
78
  // Set QRL (Queue URL)
68
79
  // Immediately updates the cache
69
80
  //
70
- exports.set = function set (qname, qrl) {
71
- qcache[qname] = qrl
72
- debug('qcache', Object.keys(qcache), 'set', qname, ' => ', qcache[qname])
81
+ export function qrlCacheSet (qname, qrl) {
82
+ qcache.set(qname, qrl)
83
+ // debug('qcache', Object.keys(qcache), 'set', qname, ' => ', qcache[qname])
73
84
  }
74
85
 
75
86
  //
76
87
  // Invalidate cache for given qname
77
88
  //
78
- exports.invalidate = function invalidate (qname) {
79
- debug('qcache', Object.keys(qcache), 'delete', qname, ' (was ', qcache[qname], ')')
80
- delete qcache[qname]
89
+ export function qrlCacheInvalidate (qname) {
90
+ // debug('qcache', Object.keys(qcache), 'delete', qname, ' (was ', qcache[qname], ')')
91
+ qcache.delete(qname)
81
92
  }
82
93
 
83
94
  //
84
95
  // Ingets multiple QRLs
85
96
  // Extracts queue names from an array of QRLs and immediately updates the cache.
86
97
  //
87
- function ingestQRLs (qrls) {
98
+ export function ingestQRLs (qrls) {
88
99
  const pairs = []
89
100
  debug('ingestQRLs', qrls)
90
- qrls.forEach(function (qrl) {
91
- const qname = path.basename((new url.URL(qrl)).pathname)
92
- qcache[qname] = qrl
93
- pairs.push({ qname: qname, qrl: qrl })
94
- debug('qcache', Object.keys(qcache), 'ingestQRLs', qname, ' => ', qcache[qname])
95
- })
101
+ for (const qrl of qrls) {
102
+ const qname = basename((new URL(qrl)).pathname)
103
+ qrlCacheSet(qname, qrl)
104
+ pairs.push({ qname, qrl })
105
+ // debug('qcache', Object.keys(qcache), 'ingestQRLs', qname, ' => ', qcache[qname])
106
+ }
96
107
  return pairs
97
108
  }
98
109
 
110
+ /**
111
+ * Returns qrls for queues matching the given prefix and regex.
112
+ */
113
+ export async function getMatchingQueues (prefix, nextToken) {
114
+ debug('getMatchingQueues', prefix, nextToken)
115
+ const input = { QueueNamePrefix: prefix, MaxResults: 1000 }
116
+ if (nextToken) input.NextToken = nextToken
117
+ const client = getSQSClient()
118
+ const command = new ListQueuesCommand(input)
119
+ const result = await client.send(command)
120
+ debug({ result })
121
+ const { QueueUrls: qrls, NextToken: keepGoing } = result || {}
122
+ debug({ qrls, keepGoing })
123
+ if (keepGoing) (qrls || []).push(...await getMatchingQueues(prefix, keepGoing))
124
+ return qrls || []
125
+ }
126
+
99
127
  //
100
128
  // Resolves into a flattened aray of {qname: ..., qrl: ...} objects.
101
129
  //
102
- exports.getQnameUrlPairs = function getQnameUrlPairs (qnames, options) {
103
- const sqs = new AWS.SQS()
104
- return Q.all(qnames.map(function (qname) {
105
- return qname.slice(-1) === '*' // wildcard queues support
106
- ? sqs
107
- .listQueues({ QueueNamePrefix: qname.slice(0, -1) })
108
- .promise()
109
- .then(function (data) {
110
- debug('listQueues return', data)
111
- if (options.fifo) {
112
- // Remove non-fifo queues
113
- data.QueueUrls = (data.QueueUrls || []).filter(url => url.slice(-fifoSuffix.length) === fifoSuffix)
114
- }
115
- return ingestQRLs(data.QueueUrls || [])
116
- })
117
- : exports
118
- .get(qname)
119
- .then(function (qrl) {
120
- debug('qrlCache.get returned', qrl)
121
- return { qname: qname, qrl: qrl }
122
- })
123
- .catch(function (err) {
124
- debug('qrlCache.get failed', err)
125
- if (options.verbose) console.error(' ' + chalk.red(qname.slice(options.prefix.length) + ' - ' + err))
126
- })
127
- }))
128
- .then(function (results) {
129
- // flatten nested results
130
- return ([].concat.apply([], results)).filter(r => r)
131
- })
130
+ export async function getQnameUrlPairs (qnames, opt) {
131
+ const promises = qnames.map(
132
+ async function getQueueUrlOrUrls (qname) {
133
+ if (qname.slice(-1) === '*') {
134
+ // Wildcard queues support
135
+ const prefix = qname.slice(0, -1)
136
+ const queues = await getMatchingQueues(prefix)
137
+ debug('listQueues return', queues)
138
+ if (opt.fifo) {
139
+ // Remove non-fifo queues
140
+ const filteredQueues = queues.filter(url => url.slice(-fifoSuffix.length) === fifoSuffix)
141
+ return ingestQRLs(filteredQueues)
142
+ } else {
143
+ return ingestQRLs(queues)
144
+ }
145
+ } else {
146
+ // Normal, non wildcard queue support
147
+ try {
148
+ return { qname, qrl: await qrlCacheGet(qname) }
149
+ } catch (err) {
150
+ if (!(err instanceof QueueDoesNotExist)) throw err
151
+ }
152
+ }
153
+ }
154
+ )
155
+
156
+ // Flatten nested results
157
+ const results = await Promise.all(promises)
158
+ return ([].concat.apply([], results)).filter(r => r)
132
159
  }
133
160
 
134
161
  debug('loaded')
package/src/sentry.js ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Routines for handling Sentry instrumentation
3
+ */
4
+
5
+ import Debug from 'debug'
6
+ import { init, captureException } from '@sentry/node'
7
+
8
+ const debug = Debug('qdone:sentry')
9
+
10
+ let sentryWasInit = false
11
+ export async function withSentry (callback, opt) {
12
+ // Bail if sentry isn't enabled
13
+ if (!opt.sentryDsn) return callback()
14
+
15
+ // Init sentry if it's not already
16
+ if (!sentryWasInit) {
17
+ init({ dsn: opt.sentryDsn, traceSampleRate: 0 })
18
+ sentryWasInit = true
19
+ }
20
+ try {
21
+ const result = await callback()
22
+ debug({ result })
23
+ return result
24
+ } catch (err) {
25
+ debug({ err })
26
+ const sentryResult = await captureException(err)
27
+ debug({ sentryResult })
28
+ throw err
29
+ }
30
+ }
package/src/sqs.js ADDED
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Functions that deal with SQS
3
+ */
4
+
5
+ import { SQSClient, ListQueuesCommand, GetQueueAttributesCommand } from '@aws-sdk/client-sqs'
6
+ import { basename } from 'path'
7
+ import Debug from 'debug'
8
+ const debug = Debug('qdone:sqs')
9
+
10
+ /**
11
+ * Utility function to return an instantiated, shared SQSClient.
12
+ */
13
+ let client
14
+ export function getSQSClient () {
15
+ if (client) return client
16
+ client = new SQSClient()
17
+ return client
18
+ }
19
+
20
+ /**
21
+ * Utility function to set the client explicitly, used in testing.
22
+ */
23
+ export function setSQSClient (explicitClient) {
24
+ client = explicitClient
25
+ }
26
+
27
+ /**
28
+ * Returns qrls for queues matching the given prefix and regex.
29
+ */
30
+ export async function getMatchingQueues (prefix, regex) {
31
+ const input = { QueueNamePrefix: prefix, MaxResults: 1000 }
32
+ const client = getSQSClient()
33
+ async function processQueues (nextToken) {
34
+ if (nextToken) input.NextToken = nextToken
35
+ const command = new ListQueuesCommand(input)
36
+ // debug({ nextToken, input, command })
37
+ const result = await client.send(command)
38
+ // debug({ result })
39
+ const { QueueUrls: qrls, NextToken: nextToken2 } = result
40
+ // debug({ qrls, nextToken2 })
41
+ return (qrls || []).filter(q => regex.test(q)).concat(nextToken2 ? await processQueues(nextToken2) : [])
42
+ }
43
+ return processQueues()
44
+ }
45
+
46
+ /**
47
+ * Gets attributes on every queue in parallel.
48
+ */
49
+ export async function getQueueAttributes (qrls) {
50
+ const promises = []
51
+ // debug({ qrls })
52
+ for (const qrl of qrls) {
53
+ const input = {
54
+ QueueUrl: qrl,
55
+ AttributeNames: [
56
+ 'ApproximateNumberOfMessages',
57
+ 'ApproximateNumberOfMessagesNotVisible',
58
+ 'ApproximateNumberOfMessagesDelayed'
59
+ ]
60
+ }
61
+ const command = new GetQueueAttributesCommand(input)
62
+ // debug({ input, command })
63
+ promises.push((async () => {
64
+ const queue = basename(qrl)
65
+ const result = await client.send(command)
66
+ // debug({ queue, result })
67
+ return { queue, result }
68
+ })())
69
+ }
70
+ return Promise.all(promises)
71
+ }
72
+
73
+ debug('loaded')