ac-sqs 0.1.0
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/.acsemver.js +9 -0
- package/.eslintrc.js +27 -0
- package/CHANGELOG.md +10 -0
- package/Makefile +17 -0
- package/README.md +132 -0
- package/index.js +209 -0
- package/package.json +26 -0
package/.acsemver.js
ADDED
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const config = {
|
|
2
|
+
root: true,
|
|
3
|
+
'env': {
|
|
4
|
+
'commonjs': true,
|
|
5
|
+
'es6': true,
|
|
6
|
+
'node': true
|
|
7
|
+
},
|
|
8
|
+
'extends': 'eslint:recommended',
|
|
9
|
+
"rules": {
|
|
10
|
+
"space-before-function-paren": 0,
|
|
11
|
+
"no-extra-semi": 0,
|
|
12
|
+
"object-curly-spacing": ["error", "always"],
|
|
13
|
+
"brace-style": ["error", "stroustrup", { "allowSingleLine": true }],
|
|
14
|
+
"no-useless-escape": 0,
|
|
15
|
+
"standard/no-callback-literal": 0,
|
|
16
|
+
"new-cap": 0
|
|
17
|
+
},
|
|
18
|
+
globals: {
|
|
19
|
+
describe: true,
|
|
20
|
+
it: true
|
|
21
|
+
},
|
|
22
|
+
'parserOptions': {
|
|
23
|
+
'ecmaVersion': 2022
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = config
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<a name="0.1.0"></a>
|
|
2
|
+
|
|
3
|
+
# [0.1.0](https://github.com/admiralcloud/ac-sqs/compare/..v0.1.0) (2023-04-29 06:14:09)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Feature
|
|
7
|
+
|
|
8
|
+
* **App:** Initial version | MP | [b4e87cdce80dd218d1be1c9dafca786d3fea15d0](https://github.com/admiralcloud/ac-sqs/commit/b4e87cdce80dd218d1be1c9dafca786d3fea15d0)
|
|
9
|
+
Initial version
|
|
10
|
+
Related issues: [admiralcloud/ac-sqs#1](https://github.com/admiralcloud/ac-sqs/issues/1) [admiralcloud/ac-api-server#340](https://github.com/admiralcloud/ac-api-server/issues/340)
|
package/Makefile
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
MOCHA_OPTS= --slow 0 -A
|
|
2
|
+
REPORTER = spec
|
|
3
|
+
|
|
4
|
+
lint-fix:
|
|
5
|
+
./node_modules/.bin/eslint --fix index.js test/test.js
|
|
6
|
+
|
|
7
|
+
lint-check:
|
|
8
|
+
./node_modules/.bin/eslint index.js test/test.js
|
|
9
|
+
|
|
10
|
+
commit:
|
|
11
|
+
@node ./node_modules/ac-semantic-release/lib/commit.js
|
|
12
|
+
|
|
13
|
+
release:
|
|
14
|
+
@node ./node_modules/ac-semantic-release/lib/release.js
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
.PHONY: check
|
package/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# AC SQS
|
|
2
|
+
This tool is a wrapper for AWS SDK's SQS function. It includes handling of big SQS messages using S3.
|
|
3
|
+
|
|
4
|
+
# Usage
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
yarn add ac-sqs
|
|
8
|
+
|
|
9
|
+
const acsqs = new ACSQS({
|
|
10
|
+
profile: 'development', // Optional AWS profile, see below
|
|
11
|
+
account: '123456789', // AWS account id
|
|
12
|
+
availableLists: [{
|
|
13
|
+
name: 'listName'
|
|
14
|
+
batchSize: 10
|
|
15
|
+
}],
|
|
16
|
+
useS3: {
|
|
17
|
+
enabled: true,
|
|
18
|
+
bucket: 'ac-sqs-messages' // s3 bucket
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
await acsqs.sendSQSMessage({ name: listName, message: 'This is my message' })
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
# Available methods/calls
|
|
26
|
+
## Initiate class
|
|
27
|
+
**Account**
|
|
28
|
+
The ***account*** id of your AWS account. This is required
|
|
29
|
+
|
|
30
|
+
**Available lists**
|
|
31
|
+
Array of AWS SQS lists that will be used by this function. Every item in the list must be an object with the following properties:
|
|
32
|
+
+ name -> the name of the list in AWS SQS. See below for more info.
|
|
33
|
+
+ batchSize -> number of messages to fetch per call. Max 10, defaults to 1
|
|
34
|
+
+ visibilityTimeout -> see AWS SQS for details, defaults to 30
|
|
35
|
+
+ waitTime -> see AWS SQS for details, defaults to 20
|
|
36
|
+
+ fifo -> set to true, if this is a fifo list
|
|
37
|
+
+ localPrefix -> set your local prefix. See below for more info
|
|
38
|
+
|
|
39
|
+
Name should be the plain name of the list. Parameters like fifo or test (in test environment) or localPrefixes (for local development) should not be part of the list name. LocalPrefix can be used if multiple developers work on your project and you want to make sure they all work on their own SQS list without changing the name of all SQS lists in your main project.
|
|
40
|
+
|
|
41
|
+
Example for a FIFO list:
|
|
42
|
+
Let the name be "mylist". This will automatically create "test_mylist" in test environment (NODE_ENV=test) and "local_LOCALPREFIX_mylist" if you set localPrefix.
|
|
43
|
+
|
|
44
|
+
**Profile [optional]**
|
|
45
|
+
By default the first AWS credentials in ~/.aws/credentials will be used. You can also export an environment variable ***profile*** or send a named AWS profile.
|
|
46
|
+
|
|
47
|
+
**useS3 [optional]**
|
|
48
|
+
AWS SQS only allows a certain message size (262kb at the time of the documentation). To process messages with a bigger payload, the actual message content will be stored in a file on AWS S3. The feature is enabled by default and you must make sure to set a bucket.
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
useS3: {
|
|
52
|
+
enabled: true,
|
|
53
|
+
bucket: 'ac-sqs-message'
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Make sure your function can read, write and delete messages in the bucket.
|
|
58
|
+
|
|
59
|
+
## sendSQSMessage
|
|
60
|
+
Create a messsage in a SQS list.
|
|
61
|
+
|
|
62
|
+
**name**
|
|
63
|
+
The name of the list to send a SQS message to.
|
|
64
|
+
|
|
65
|
+
**message**
|
|
66
|
+
The actual message (as plain test). If you want to send JSON, please stringify!
|
|
67
|
+
|
|
68
|
+
**messageGroupId [optional]**
|
|
69
|
+
See AWS SQS for details
|
|
70
|
+
|
|
71
|
+
**deDuplicationId [optional]**
|
|
72
|
+
See AWS SQS for details
|
|
73
|
+
|
|
74
|
+
**delay [optional]**
|
|
75
|
+
See AWS SQS for details
|
|
76
|
+
|
|
77
|
+
https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SendMessage.html
|
|
78
|
+
|
|
79
|
+
## receiveSQSMessages
|
|
80
|
+
Receive messages from SQS. By default up to 10 messages are fetched per call. You set a lower number using the batchSize parameter in the corresponding availableLists entry.
|
|
81
|
+
|
|
82
|
+
**name**
|
|
83
|
+
The name of the list to receive a SQS messages from.
|
|
84
|
+
|
|
85
|
+
https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_ReceiveMessage.html
|
|
86
|
+
|
|
87
|
+
## deleteSQSMessages
|
|
88
|
+
Deletes one or multiple SQS messages and also cleans up/deletes related S3 files.
|
|
89
|
+
|
|
90
|
+
**name**
|
|
91
|
+
The name of the list to delete SQS messages from.
|
|
92
|
+
|
|
93
|
+
**items**
|
|
94
|
+
An array of objects to delete. Every objects must a least have properties ***Id*** and ***ReceiptHandle***. If the messages was originally stored on S3 the entry must also contain ***Key***.
|
|
95
|
+
|
|
96
|
+
The ***Id*** parameter is the message id, ***ReceiptHandle*** is an identifier you get with the receive request.
|
|
97
|
+
|
|
98
|
+
https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_DeleteMessageBatch.html
|
|
99
|
+
|
|
100
|
+
## getQueueAttributes
|
|
101
|
+
Get information about a list
|
|
102
|
+
|
|
103
|
+
**name**
|
|
104
|
+
The name of the list to get information about.
|
|
105
|
+
|
|
106
|
+
**attributes**
|
|
107
|
+
An array of metadata to get. By default only "ApproximateNumberOfMessages" is requested. Please see AWS SQS for other metadata options.
|
|
108
|
+
|
|
109
|
+
https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_GetQueueAttributes.html
|
|
110
|
+
|
|
111
|
+
# Test
|
|
112
|
+
You can run tests using **yarn run test**.
|
|
113
|
+
|
|
114
|
+
Preparations you have to make before running the tests:
|
|
115
|
+
|
|
116
|
+
+ export the AWS profile to use for tests (if it is not your default profile) using **export profile=development**
|
|
117
|
+
+ export the AWS account id using **export awsaccount=12345**
|
|
118
|
+
+ create a SQS list named "test_acsqs"
|
|
119
|
+
+ create a bucket and export the name using **export bucket=acsqs-test-bucket**
|
|
120
|
+
|
|
121
|
+
**ATTENTION**: Tests may fail when checking the SQS length. This is a by-design failure:
|
|
122
|
+
"ApproximateNumberOfMessages metrics may not achieve consistency until at least 1 minute after the producers stop sending messages."
|
|
123
|
+
|
|
124
|
+
See https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_GetQueueAttributes.html
|
|
125
|
+
|
|
126
|
+
# Misc
|
|
127
|
+
## Links
|
|
128
|
+
- [Website](https://www.admiralcloud.com/)
|
|
129
|
+
- [Facebook](https://www.facebook.com/MediaAssetManagement/)
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
[MIT License](https://opensource.org/licenses/MIT) Copyright © 2009-present, AdmiralCloud AG, Mark Poepping
|
package/index.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
const _ = require('lodash')
|
|
2
|
+
const { v4: uuidV4 } = require('uuid')
|
|
3
|
+
const https = require('https')
|
|
4
|
+
|
|
5
|
+
const { fromNodeProviderChain } = require('@aws-sdk/credential-providers')
|
|
6
|
+
const { SQSClient, SendMessageCommand, ReceiveMessageCommand, DeleteMessageBatchCommand, GetQueueAttributesCommand } = require('@aws-sdk/client-sqs')
|
|
7
|
+
const { S3Client, GetObjectCommand, PutObjectCommand, DeleteObjectsCommand } = require("@aws-sdk/client-s3")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ACSQS {
|
|
11
|
+
constructor({ region = 'eu-central-1', account, availableLists, profile = process.env['profile'], useS3 = { enabled: true, bucket: undefined }, messageThreshold = 250e3, debug, logger=console }) {
|
|
12
|
+
const httpOptions = {
|
|
13
|
+
keepAlive: true
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
this.region = region
|
|
17
|
+
this.account = account
|
|
18
|
+
this.availableLists = availableLists
|
|
19
|
+
this.logger = logger
|
|
20
|
+
|
|
21
|
+
const awsConfig = {
|
|
22
|
+
region,
|
|
23
|
+
credentials: fromNodeProviderChain({ profile, ignoreCache: true }),
|
|
24
|
+
httpOptions: {
|
|
25
|
+
agent: new https.Agent(httpOptions)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (debug && profile) {
|
|
29
|
+
this.logger.log('ACSQS | Using AWS profile | %s', profile)
|
|
30
|
+
}
|
|
31
|
+
this.sqs = new SQSClient(awsConfig)
|
|
32
|
+
|
|
33
|
+
// store huge messages in S3 and let SQS be the link to that message
|
|
34
|
+
if (_.get(useS3, 'enabled')) {
|
|
35
|
+
this.useS3 = true
|
|
36
|
+
this.messageThreshold = messageThreshold
|
|
37
|
+
this.bucket = _.get(useS3, 'bucket')
|
|
38
|
+
this.s3 = new S3Client(awsConfig)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async getQueueUrl({ name, fifo, localPrefix }) {
|
|
43
|
+
let queueUrl = `https://sqs.${this.region}.amazonaws.com/${this.account}/`
|
|
44
|
+
if (localPrefix) queueUrl += `local_${localPrefix}_`
|
|
45
|
+
if (process.env['NODE_ENV'] === 'test') queueUrl += 'test_'
|
|
46
|
+
queueUrl += name
|
|
47
|
+
if (fifo) queueUrl += '.fifo'
|
|
48
|
+
return queueUrl
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async getQueueAttributes({ name, attributes = ['ApproximateNumberOfMessages'] }) {
|
|
52
|
+
const config = _.find(this.availableLists, { name })
|
|
53
|
+
if (!config) {
|
|
54
|
+
this.logger.error('ACSQS | getQueueAttributes | configurationMissing | %s', name)
|
|
55
|
+
throw new Error('configurationForListMissing')
|
|
56
|
+
}
|
|
57
|
+
let sqsParams = {
|
|
58
|
+
QueueUrl: await this.getQueueUrl(config),
|
|
59
|
+
AttributeNames: attributes
|
|
60
|
+
}
|
|
61
|
+
const command = new GetQueueAttributesCommand(sqsParams)
|
|
62
|
+
try {
|
|
63
|
+
return await this.sqs.send(command)
|
|
64
|
+
}
|
|
65
|
+
catch(e) {
|
|
66
|
+
this.logger.error('ACSQS | getQueueAttributes | %s | %s', name, e?.message)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
async sendSQSMessage({ name, message, messageGroupId, deDuplicationId, delay }) {
|
|
72
|
+
const config = _.find(this.availableLists, { name })
|
|
73
|
+
if (!config) {
|
|
74
|
+
this.logger.error('AWS | sendSQSMessage | configurationMissing | %s', name)
|
|
75
|
+
throw new Error('configurationForListMissing')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (this.useS3 && message.length > this.messageThreshold) {
|
|
79
|
+
// store message in S3
|
|
80
|
+
const key = uuidV4()
|
|
81
|
+
const input = {
|
|
82
|
+
Bucket: this.bucket,
|
|
83
|
+
Key: key,
|
|
84
|
+
ContentType: 'text/plain',
|
|
85
|
+
Body: Buffer.from(message, 'utf-8')
|
|
86
|
+
}
|
|
87
|
+
const command = new PutObjectCommand(input)
|
|
88
|
+
await this.s3.send(command)
|
|
89
|
+
message = `s3:${key}`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const sqsParams = {
|
|
93
|
+
QueueUrl: await this.getQueueUrl(config),
|
|
94
|
+
MessageBody: message
|
|
95
|
+
}
|
|
96
|
+
if (messageGroupId) _.set(sqsParams, 'MessageGroupId', messageGroupId)
|
|
97
|
+
if (deDuplicationId) _.set(sqsParams, 'MessageDeduplicationId', deDuplicationId)
|
|
98
|
+
if (delay) _.set(sqsParams, 'DelaySeconds', delay)
|
|
99
|
+
|
|
100
|
+
const command = new SendMessageCommand(sqsParams)
|
|
101
|
+
try {
|
|
102
|
+
const response = await this.sqs.send(command)
|
|
103
|
+
return response
|
|
104
|
+
}
|
|
105
|
+
catch(e) {
|
|
106
|
+
this.logger.error('ACSQS | sendSQSMessage | %s | %s', name, e?.message)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async receiveSQSMessages({ name }) {
|
|
111
|
+
const config = _.find(this.availableLists, { name })
|
|
112
|
+
if (!config) {
|
|
113
|
+
this.logger.error('AWS | receiveSQSMessage | configurationMissing | %s', name)
|
|
114
|
+
throw new Error('configurationForListMissing')
|
|
115
|
+
}
|
|
116
|
+
let sqsParams = {
|
|
117
|
+
QueueUrl: await this.getQueueUrl(config),
|
|
118
|
+
MaxNumberOfMessages: _.get(config, 'batchSize', 10),
|
|
119
|
+
VisibilityTimeout: _.get(config, 'visibilityTimeout', 30),
|
|
120
|
+
WaitTimeSeconds: _.get(config, 'waitTime', 20)
|
|
121
|
+
}
|
|
122
|
+
const command = new ReceiveMessageCommand(sqsParams)
|
|
123
|
+
try {
|
|
124
|
+
const result = await this.sqs.send(command)
|
|
125
|
+
const messages = await Promise.all(result.Messages.map(async message => {
|
|
126
|
+
if (message.Body.startsWith('s3:')) {
|
|
127
|
+
const key = message.Body.replace('s3:', '')
|
|
128
|
+
try {
|
|
129
|
+
const objectData = await this.fetchS3Object({ key })
|
|
130
|
+
message.Body = objectData
|
|
131
|
+
message.s3key = key
|
|
132
|
+
}
|
|
133
|
+
catch(e) {
|
|
134
|
+
this.logger.error('AWS | receiveSQSMessages | s3KeyInvalid | %s', name, key)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return message
|
|
138
|
+
}))
|
|
139
|
+
return messages
|
|
140
|
+
}
|
|
141
|
+
catch(e) {
|
|
142
|
+
this.logger.error('ACSQS | receiveSQSMessage | %s | %s', name, e?.message)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// items -> [{ Id, ReceiptHandle }]
|
|
147
|
+
async deleteSQSMessages({ name, items }) {
|
|
148
|
+
const config = _.find(this.availableLists, { name })
|
|
149
|
+
if (!config) {
|
|
150
|
+
this.logger.error('AWS | deleteSQSMessage | configurationMissing | %s', name)
|
|
151
|
+
throw new Error('configurationForListMissing')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const entries = []
|
|
155
|
+
const s3keys = []
|
|
156
|
+
for (const item of items) {
|
|
157
|
+
entries.push({ Id: item.Id, ReceiptHandle: item.ReceiptHandle })
|
|
158
|
+
if (item.s3key) {
|
|
159
|
+
s3keys.push({ Key: item.s3key })
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let sqsParams = {
|
|
164
|
+
QueueUrl: await this.getQueueUrl(config),
|
|
165
|
+
Entries: entries
|
|
166
|
+
}
|
|
167
|
+
const command = new DeleteMessageBatchCommand(sqsParams)
|
|
168
|
+
try {
|
|
169
|
+
const response = await this.sqs.send(command)
|
|
170
|
+
// check cleaning up s3
|
|
171
|
+
if (this.useS3 && _.size(s3keys)) {
|
|
172
|
+
const input = {
|
|
173
|
+
Bucket: this.bucket,
|
|
174
|
+
Delete: {
|
|
175
|
+
Objects: s3keys,
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const command = new DeleteObjectsCommand(input);
|
|
179
|
+
this.s3.send(command)
|
|
180
|
+
}
|
|
181
|
+
return response
|
|
182
|
+
}
|
|
183
|
+
catch(e) {
|
|
184
|
+
this.logger.error('ACSQS | deleteSQSMessage | %s | %s', name, e?.message)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
// helpers
|
|
190
|
+
async fetchS3Object({ key }) {
|
|
191
|
+
const input = {
|
|
192
|
+
Bucket: this.bucket,
|
|
193
|
+
Key: key
|
|
194
|
+
}
|
|
195
|
+
const command = new GetObjectCommand(input)
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const response = await this.s3.send(command)
|
|
199
|
+
return await response.Body.transformToString()
|
|
200
|
+
}
|
|
201
|
+
catch (e) {
|
|
202
|
+
this.logger.error('ACSQS | fetchS3Object | %j | %s', input, e?.message)
|
|
203
|
+
throw e
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
module.exports = ACSQS
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ac-sqs",
|
|
3
|
+
"author": "Mark Poepping (https://www.admiralcloud.com)",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": "admiralcloud/ac-sqs",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@aws-sdk/client-s3": "^3.321.1",
|
|
9
|
+
"@aws-sdk/client-sqs": "^3.321.1",
|
|
10
|
+
"@aws-sdk/credential-providers": "^3.321.1",
|
|
11
|
+
"lodash": "^4.17.21",
|
|
12
|
+
"uuid": "^9.0.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"ac-semantic-release": "^0.4.1",
|
|
16
|
+
"chai": "^4.3.7",
|
|
17
|
+
"eslint": "^8.39.0",
|
|
18
|
+
"mocha": "^10.x"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "mocha --reporter spec"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=16.0.0"
|
|
25
|
+
}
|
|
26
|
+
}
|