sqs-producer 2.2.0 → 3.0.0-alpha.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.
- package/.eslintignore +4 -0
- package/.github/ISSUE_TEMPLATE/bug-report.md +2 -2
- package/.github/ISSUE_TEMPLATE/feature-request.md +0 -1
- package/.github/ISSUE_TEMPLATE/technical-question.md +0 -1
- package/.prettierignore +4 -0
- package/.prettierrc.js +5 -0
- package/README.md +54 -23
- package/dist/producer.d.ts +4 -5
- package/dist/producer.js +13 -9
- package/dist/types.d.ts +4 -3
- package/dist/types.js +11 -10
- package/package.json +37 -5
- package/src/producer.ts +56 -23
- package/src/types.ts +74 -61
- package/src/validation.ts +9 -10
- package/tsconfig.json +2 -7
package/.eslintignore
ADDED
|
@@ -4,7 +4,6 @@ about: Create a report to help us improve
|
|
|
4
4
|
title: ''
|
|
5
5
|
labels: bug
|
|
6
6
|
assignees: ''
|
|
7
|
-
|
|
8
7
|
---
|
|
9
8
|
|
|
10
9
|
**Describe the bug**
|
|
@@ -12,6 +11,7 @@ A clear and concise description of what the bug is.
|
|
|
12
11
|
|
|
13
12
|
**To Reproduce**
|
|
14
13
|
Steps to reproduce the behaviour:
|
|
14
|
+
|
|
15
15
|
1. Go to '...'
|
|
16
16
|
2. Select '....'
|
|
17
17
|
3. Scroll down to '....'
|
|
@@ -24,4 +24,4 @@ A clear and concise description of what you expected to happen.
|
|
|
24
24
|
If applicable, add screenshots to help explain your problem.
|
|
25
25
|
|
|
26
26
|
**Additional context**
|
|
27
|
-
Add any other context about the problem here, such as specific device information.
|
|
27
|
+
Add any other context about the problem here, such as specific device information.
|
package/.prettierignore
ADDED
package/.prettierrc.js
ADDED
package/README.md
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
sqs-producer
|
|
2
|
-
====================
|
|
1
|
+
# sqs-producer
|
|
3
2
|
|
|
4
3
|
[](https://npmjs.org/package/sqs-producer)
|
|
5
4
|
[](https://github.com/bbc/sqs-producer/actions/workflows/test.yml)
|
|
6
|
-
[](https://codeclimate.com/github/BBC/sqs-producer)
|
|
5
|
+
[](https://codeclimate.com/github/BBC/sqs-producer)
|
|
7
6
|
[](https://codeclimate.com/github/BBC/sqs-producer)
|
|
8
7
|
|
|
9
8
|
Enqueues messages onto a given SQS queue
|
|
@@ -13,11 +12,19 @@ Enqueues messages onto a given SQS queue
|
|
|
13
12
|
```
|
|
14
13
|
npm install sqs-producer
|
|
15
14
|
```
|
|
15
|
+
|
|
16
|
+
> **Note**
|
|
17
|
+
> This library assumes you are using [AWS SDK v3](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-sqs/index.html). If you are using v2, please install v2.2.0:
|
|
18
|
+
>
|
|
19
|
+
> ```bash
|
|
20
|
+
> npm install sqs-producer@5.8.0 --save-dev
|
|
21
|
+
> ```
|
|
22
|
+
|
|
16
23
|
## Usage
|
|
17
24
|
|
|
18
25
|
```js
|
|
19
|
-
|
|
20
|
-
import
|
|
26
|
+
import { Producer } from 'sqs-producer';
|
|
27
|
+
import { SQSClient } from '@aws-sdk/client-sqs';
|
|
21
28
|
|
|
22
29
|
// create simple producer
|
|
23
30
|
const producer = Producer.create({
|
|
@@ -25,19 +32,6 @@ const producer = Producer.create({
|
|
|
25
32
|
region: 'eu-west-1'
|
|
26
33
|
});
|
|
27
34
|
|
|
28
|
-
// create custom producer (supporting all opts as per the API docs: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/SQS.html#constructor-property)
|
|
29
|
-
AWS.config.update({
|
|
30
|
-
accessKeyId: 'yourAccessKey',
|
|
31
|
-
secretAccessKey: 'yourSecret',
|
|
32
|
-
region: 'eu-west-1',
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const producer = Producer.create({
|
|
36
|
-
queueUrl: 'https://sqs.eu-west-1.amazonaws.com/account-id/queue-name',
|
|
37
|
-
region: 'eu-west-1',
|
|
38
|
-
sqs: new AWS.SQS(),
|
|
39
|
-
});
|
|
40
|
-
|
|
41
35
|
// send messages to the queue
|
|
42
36
|
await producer.send(['msg1', 'msg2']);
|
|
43
37
|
|
|
@@ -46,10 +40,12 @@ const size = await producer.queueSize();
|
|
|
46
40
|
console.log(`There are ${size} messages on the queue.`);
|
|
47
41
|
|
|
48
42
|
// send a message to the queue with a specific ID (by default the body is used as the ID)
|
|
49
|
-
await producer.send([
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
43
|
+
await producer.send([
|
|
44
|
+
{
|
|
45
|
+
id: 'id1',
|
|
46
|
+
body: 'Hello world'
|
|
47
|
+
}
|
|
48
|
+
]);
|
|
53
49
|
|
|
54
50
|
// send a message to the queue with
|
|
55
51
|
// - delaySeconds (must be an number contained within 0 and 900)
|
|
@@ -80,13 +76,45 @@ await producer.send([
|
|
|
80
76
|
//
|
|
81
77
|
// http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queue-recommendations.html
|
|
82
78
|
await producer.send({
|
|
83
|
-
id:
|
|
79
|
+
id: 'testId',
|
|
84
80
|
body: 'Hello world from our FIFO queue!',
|
|
85
81
|
groupId: 'group1234',
|
|
86
82
|
deduplicationId: 'abcdef123456' // typically a hash of the message body
|
|
87
83
|
});
|
|
88
84
|
```
|
|
89
85
|
|
|
86
|
+
### Credentials
|
|
87
|
+
|
|
88
|
+
By default the consumer will look for AWS credentials in the places [specified by the AWS SDK](http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/node-configuring.html#Setting_AWS_Credentials). The simplest option is to export your credentials as environment variables:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
export AWS_SECRET_ACCESS_KEY=...
|
|
92
|
+
export AWS_ACCESS_KEY_ID=...
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
If you need to specify your credentials manually, you can use a pre-configured instance of the [SQS Client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-sqs/classes/sqsclient.html) client.
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
import { Producer } from 'sqs-producer';
|
|
99
|
+
import { SQSClient } from '@aws-sdk/client-sqs';
|
|
100
|
+
|
|
101
|
+
// create simple producer
|
|
102
|
+
const producer = Producer.create({
|
|
103
|
+
queueUrl: 'https://sqs.eu-west-1.amazonaws.com/account-id/queue-name',
|
|
104
|
+
region: 'eu-west-1',
|
|
105
|
+
sqs: new SQSClient({
|
|
106
|
+
region: 'my-region',
|
|
107
|
+
credentials: {
|
|
108
|
+
accessKeyId: 'yourAccessKey',
|
|
109
|
+
secretAccessKey: 'yourSecret'
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// send messages to the queue
|
|
115
|
+
await producer.send(['msg1', 'msg2']);
|
|
116
|
+
```
|
|
117
|
+
|
|
90
118
|
## Development
|
|
91
119
|
|
|
92
120
|
### Test
|
|
@@ -96,6 +124,7 @@ npm test
|
|
|
96
124
|
```
|
|
97
125
|
|
|
98
126
|
### Coverage
|
|
127
|
+
|
|
99
128
|
For coverage report, run the command:
|
|
100
129
|
|
|
101
130
|
```
|
|
@@ -103,6 +132,7 @@ npm run coverage
|
|
|
103
132
|
```
|
|
104
133
|
|
|
105
134
|
### Lint
|
|
135
|
+
|
|
106
136
|
To check for problems using ESLint
|
|
107
137
|
|
|
108
138
|
```
|
|
@@ -110,4 +140,5 @@ npm run lint
|
|
|
110
140
|
```
|
|
111
141
|
|
|
112
142
|
## Contributing
|
|
143
|
+
|
|
113
144
|
See [contributing guildlines](./.github/CONTRIBUTING.md)
|
package/dist/producer.d.ts
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { SendMessageBatchResultEntryList } from 'aws-sdk/clients/sqs';
|
|
1
|
+
import { SQSClient, SendMessageBatchResultEntry } from '@aws-sdk/client-sqs';
|
|
3
2
|
import { Message } from './types';
|
|
4
3
|
interface ProducerOptions {
|
|
5
4
|
queueUrl?: string;
|
|
6
5
|
batchSize?: number;
|
|
7
|
-
sqs?:
|
|
6
|
+
sqs?: SQSClient;
|
|
8
7
|
region?: string;
|
|
9
8
|
}
|
|
10
9
|
export declare class Producer {
|
|
11
10
|
static create: (options: ProducerOptions) => Producer;
|
|
12
11
|
queueUrl: string;
|
|
13
12
|
batchSize: number;
|
|
14
|
-
sqs:
|
|
13
|
+
sqs: SQSClient;
|
|
15
14
|
region?: string;
|
|
16
15
|
constructor(options: ProducerOptions);
|
|
17
16
|
queueSize(): Promise<number>;
|
|
18
|
-
send(messages: string | Message | (string | Message)[]): Promise<
|
|
17
|
+
send(messages: string | Message | (string | Message)[]): Promise<SendMessageBatchResultEntry[]>;
|
|
19
18
|
private validate;
|
|
20
19
|
private sendBatch;
|
|
21
20
|
}
|
package/dist/producer.js
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Producer = void 0;
|
|
4
|
-
const
|
|
4
|
+
const client_sqs_1 = require("@aws-sdk/client-sqs");
|
|
5
5
|
const types_1 = require("./types");
|
|
6
|
-
const requiredOptions = [
|
|
7
|
-
'queueUrl'
|
|
8
|
-
];
|
|
6
|
+
const requiredOptions = ['queueUrl'];
|
|
9
7
|
class Producer {
|
|
10
8
|
constructor(options) {
|
|
11
9
|
this.validate(options);
|
|
12
10
|
this.queueUrl = options.queueUrl;
|
|
13
11
|
this.batchSize = options.batchSize || 10;
|
|
14
|
-
this.sqs =
|
|
12
|
+
this.sqs =
|
|
13
|
+
options.sqs ||
|
|
14
|
+
new client_sqs_1.SQSClient(Object.assign(Object.assign({}, options), { region: options.region || 'eu-west-1' }));
|
|
15
15
|
}
|
|
16
16
|
async queueSize() {
|
|
17
|
-
const
|
|
17
|
+
const command = new client_sqs_1.GetQueueAttributesCommand({
|
|
18
18
|
QueueUrl: this.queueUrl,
|
|
19
19
|
AttributeNames: ['ApproximateNumberOfMessages']
|
|
20
|
-
})
|
|
21
|
-
|
|
20
|
+
});
|
|
21
|
+
const result = await this.sqs.send(command);
|
|
22
|
+
return Number(result &&
|
|
23
|
+
result.Attributes &&
|
|
24
|
+
result.Attributes.ApproximateNumberOfMessages);
|
|
22
25
|
}
|
|
23
26
|
async send(messages) {
|
|
24
27
|
const failedMessages = [];
|
|
@@ -44,7 +47,8 @@ class Producer {
|
|
|
44
47
|
QueueUrl: this.queueUrl,
|
|
45
48
|
Entries: batch.map(types_1.toEntry)
|
|
46
49
|
};
|
|
47
|
-
const
|
|
50
|
+
const command = new client_sqs_1.SendMessageBatchCommand(params);
|
|
51
|
+
const result = await this.sqs.send(command);
|
|
48
52
|
const failedMessagesBatch = failedMessages.concat(result.Failed.map((entry) => entry.Id));
|
|
49
53
|
const successfulMessagesBatch = successfulMessages.concat(result.Successful);
|
|
50
54
|
if (endIndex < messages.length) {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { SendMessageBatchRequestEntry } from 'aws-sdk/clients/sqs';
|
|
1
|
+
import { SendMessageBatchRequestEntry, MessageAttributeValue } from '@aws-sdk/client-sqs';
|
|
3
2
|
export interface Message {
|
|
4
3
|
id: string;
|
|
5
4
|
body: string;
|
|
6
5
|
groupId?: string;
|
|
7
6
|
deduplicationId?: string;
|
|
8
7
|
delaySeconds?: number;
|
|
9
|
-
messageAttributes?:
|
|
8
|
+
messageAttributes?: {
|
|
9
|
+
[key: string]: MessageAttributeValue;
|
|
10
|
+
};
|
|
10
11
|
}
|
|
11
12
|
export declare function toEntry(message: string | Message): SendMessageBatchRequestEntry;
|
package/dist/types.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.toEntry = void 0;
|
|
4
|
-
const
|
|
4
|
+
const validation_1 = require("./validation");
|
|
5
5
|
function entryFromObject(message) {
|
|
6
6
|
if (!message.body) {
|
|
7
7
|
throw new Error(`Object messages must have 'body' prop`);
|
|
@@ -13,7 +13,7 @@ function entryFromObject(message) {
|
|
|
13
13
|
throw new Error(`FIFO Queue messages must have 'groupId' prop`);
|
|
14
14
|
}
|
|
15
15
|
if (message.id) {
|
|
16
|
-
if (!isString(message.id)) {
|
|
16
|
+
if (!(0, validation_1.isString)(message.id)) {
|
|
17
17
|
throw new Error('Message.id value must be a string');
|
|
18
18
|
}
|
|
19
19
|
}
|
|
@@ -22,27 +22,28 @@ function entryFromObject(message) {
|
|
|
22
22
|
MessageBody: message.body
|
|
23
23
|
};
|
|
24
24
|
if (message.delaySeconds) {
|
|
25
|
-
if (
|
|
26
|
-
|
|
25
|
+
if (typeof message.delaySeconds !== 'number' ||
|
|
26
|
+
message.delaySeconds < 0 ||
|
|
27
|
+
message.delaySeconds > 900) {
|
|
27
28
|
throw new Error('Message.delaySeconds value must be a number contained within [0 - 900]');
|
|
28
29
|
}
|
|
29
30
|
entry.DelaySeconds = message.delaySeconds;
|
|
30
31
|
}
|
|
31
32
|
if (message.messageAttributes) {
|
|
32
|
-
if (!isObject(message.messageAttributes)) {
|
|
33
|
+
if (!(0, validation_1.isObject)(message.messageAttributes)) {
|
|
33
34
|
throw new Error('Message.messageAttributes must be an object');
|
|
34
35
|
}
|
|
35
|
-
Object.values(message.messageAttributes).every(isMessageAttributeValid);
|
|
36
|
+
Object.values(message.messageAttributes).every(validation_1.isMessageAttributeValid);
|
|
36
37
|
entry.MessageAttributes = message.messageAttributes;
|
|
37
38
|
}
|
|
38
39
|
if (message.groupId) {
|
|
39
|
-
if (!isString(message.groupId)) {
|
|
40
|
+
if (!(0, validation_1.isString)(message.groupId)) {
|
|
40
41
|
throw new Error('Message.groupId value must be a string');
|
|
41
42
|
}
|
|
42
43
|
entry.MessageGroupId = message.groupId;
|
|
43
44
|
}
|
|
44
45
|
if (message.deduplicationId) {
|
|
45
|
-
if (!isString(message.deduplicationId)) {
|
|
46
|
+
if (!(0, validation_1.isString)(message.deduplicationId)) {
|
|
46
47
|
throw new Error('Message.deduplicationId value must be a string');
|
|
47
48
|
}
|
|
48
49
|
entry.MessageDeduplicationId = message.deduplicationId;
|
|
@@ -56,10 +57,10 @@ function entryFromString(message) {
|
|
|
56
57
|
};
|
|
57
58
|
}
|
|
58
59
|
function toEntry(message) {
|
|
59
|
-
if (isString(message)) {
|
|
60
|
+
if ((0, validation_1.isString)(message)) {
|
|
60
61
|
return entryFromString(message);
|
|
61
62
|
}
|
|
62
|
-
if (isObject(message)) {
|
|
63
|
+
if ((0, validation_1.isObject)(message)) {
|
|
63
64
|
return entryFromObject(message);
|
|
64
65
|
}
|
|
65
66
|
throw new Error('A message can either be an object or a string');
|
package/package.json
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sqs-producer",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0-alpha.1",
|
|
4
4
|
"description": "Enqueues messages onto a given SQS queue",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"test": "mocha --recursive --full-trace --exit",
|
|
9
|
-
"posttest": "npm run lint",
|
|
9
|
+
"posttest": "npm run lint && npm run format:check",
|
|
10
10
|
"coverage": "nyc mocha && nyc report --reporter=html && nyc report --reporter=json-summary",
|
|
11
11
|
"lcov": "nyc mocha && nyc report --reporter=lcov",
|
|
12
|
-
"lint": "
|
|
12
|
+
"lint": "eslint . --ext .ts",
|
|
13
|
+
"lint:fix": "eslint . --fix",
|
|
14
|
+
"format": "prettier --loglevel warn --write \"**/*.{js,json,jsx,md,ts,tsx,html}\"",
|
|
15
|
+
"format:check": "prettier --check \"**/*.{js,json,jsx,md,ts,tsx,html}\"",
|
|
13
16
|
"build": "npm run clean && tsc",
|
|
14
17
|
"prepublish": "npm run build",
|
|
15
18
|
"pretest": "npm run build",
|
|
@@ -41,17 +44,21 @@
|
|
|
41
44
|
"@types/node": "^18.11.12",
|
|
42
45
|
"@types/sinon": "^10.0.13",
|
|
43
46
|
"chai": "^4.3.7",
|
|
47
|
+
"eslint": "^8.29.0",
|
|
48
|
+
"eslint-config-iplayer-ts": "^4.1.0",
|
|
49
|
+
"eslint-config-prettier": "^4.3.0",
|
|
44
50
|
"mocha": "^10.1.0",
|
|
45
51
|
"nyc": "^15.1.0",
|
|
52
|
+
"prettier": "^2.8.1",
|
|
46
53
|
"sinon": "^15.0.0",
|
|
47
54
|
"ts-node": "^10.9.1",
|
|
48
55
|
"typescript": "^4.9.4"
|
|
49
56
|
},
|
|
50
57
|
"dependencies": {
|
|
51
|
-
"aws-sdk": "^
|
|
58
|
+
"@aws-sdk/client-sqs": "^3.226.0"
|
|
52
59
|
},
|
|
53
60
|
"peerDependencies": {
|
|
54
|
-
"aws-sdk": "^
|
|
61
|
+
"@aws-sdk/client-sqs": "^3.226.0"
|
|
55
62
|
},
|
|
56
63
|
"mocha": {
|
|
57
64
|
"spec": "test/**/**/*.test.ts",
|
|
@@ -69,5 +76,30 @@
|
|
|
69
76
|
],
|
|
70
77
|
"sourceMap": true,
|
|
71
78
|
"instrument": true
|
|
79
|
+
},
|
|
80
|
+
"eslintConfig": {
|
|
81
|
+
"extends": [
|
|
82
|
+
"iplayer-ts",
|
|
83
|
+
"prettier",
|
|
84
|
+
"prettier/react",
|
|
85
|
+
"prettier/@typescript-eslint"
|
|
86
|
+
],
|
|
87
|
+
"parserOptions": {
|
|
88
|
+
"sourceType": "module"
|
|
89
|
+
},
|
|
90
|
+
"rules": {
|
|
91
|
+
"@typescript-eslint/naming-convention": [
|
|
92
|
+
"error",
|
|
93
|
+
{
|
|
94
|
+
"selector": "variable",
|
|
95
|
+
"format": [
|
|
96
|
+
"camelCase",
|
|
97
|
+
"UPPER_CASE",
|
|
98
|
+
"PascalCase"
|
|
99
|
+
],
|
|
100
|
+
"leadingUnderscore": "allow"
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
72
104
|
}
|
|
73
105
|
}
|
package/src/producer.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
SQSClient,
|
|
3
|
+
SendMessageBatchResultEntry,
|
|
4
|
+
SendMessageBatchCommand,
|
|
5
|
+
GetQueueAttributesCommand
|
|
6
|
+
} from '@aws-sdk/client-sqs';
|
|
3
7
|
import { Message, toEntry } from './types';
|
|
4
|
-
const requiredOptions = [
|
|
5
|
-
'queueUrl'
|
|
6
|
-
];
|
|
8
|
+
const requiredOptions = ['queueUrl'];
|
|
7
9
|
|
|
8
10
|
interface ProducerOptions {
|
|
9
11
|
queueUrl?: string;
|
|
10
12
|
batchSize?: number;
|
|
11
|
-
sqs?:
|
|
13
|
+
sqs?: SQSClient;
|
|
12
14
|
region?: string;
|
|
13
15
|
}
|
|
14
16
|
|
|
@@ -16,35 +18,50 @@ export class Producer {
|
|
|
16
18
|
static create: (options: ProducerOptions) => Producer;
|
|
17
19
|
queueUrl: string;
|
|
18
20
|
batchSize: number;
|
|
19
|
-
sqs:
|
|
21
|
+
sqs: SQSClient;
|
|
20
22
|
region?: string;
|
|
21
23
|
|
|
22
24
|
constructor(options: ProducerOptions) {
|
|
23
25
|
this.validate(options);
|
|
24
26
|
this.queueUrl = options.queueUrl;
|
|
25
27
|
this.batchSize = options.batchSize || 10;
|
|
26
|
-
this.sqs =
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
this.sqs =
|
|
29
|
+
options.sqs ||
|
|
30
|
+
new SQSClient({
|
|
31
|
+
...options,
|
|
32
|
+
region: options.region || 'eu-west-1'
|
|
33
|
+
});
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
async queueSize(): Promise<number> {
|
|
33
|
-
const
|
|
37
|
+
const command = new GetQueueAttributesCommand({
|
|
34
38
|
QueueUrl: this.queueUrl,
|
|
35
39
|
AttributeNames: ['ApproximateNumberOfMessages']
|
|
36
|
-
})
|
|
40
|
+
});
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
const result = await this.sqs.send(command);
|
|
43
|
+
|
|
44
|
+
return Number(
|
|
45
|
+
result &&
|
|
46
|
+
result.Attributes &&
|
|
47
|
+
result.Attributes.ApproximateNumberOfMessages
|
|
48
|
+
);
|
|
39
49
|
}
|
|
40
50
|
|
|
41
|
-
async send(
|
|
51
|
+
async send(
|
|
52
|
+
messages: string | Message | (string | Message)[]
|
|
53
|
+
): Promise<SendMessageBatchResultEntry[]> {
|
|
42
54
|
const failedMessages = [];
|
|
43
55
|
const successfulMessages = [];
|
|
44
56
|
const startIndex = 0;
|
|
45
57
|
const messagesArr = !Array.isArray(messages) ? [messages] : messages;
|
|
46
58
|
|
|
47
|
-
return this.sendBatch(
|
|
59
|
+
return this.sendBatch(
|
|
60
|
+
failedMessages,
|
|
61
|
+
successfulMessages,
|
|
62
|
+
messagesArr,
|
|
63
|
+
startIndex
|
|
64
|
+
);
|
|
48
65
|
}
|
|
49
66
|
|
|
50
67
|
private validate(options: ProducerOptions): void {
|
|
@@ -58,7 +75,12 @@ export class Producer {
|
|
|
58
75
|
}
|
|
59
76
|
}
|
|
60
77
|
|
|
61
|
-
private async sendBatch(
|
|
78
|
+
private async sendBatch(
|
|
79
|
+
failedMessages?: string[],
|
|
80
|
+
successfulMessages?: SendMessageBatchResultEntry[],
|
|
81
|
+
messages?: (string | Message)[],
|
|
82
|
+
startIndex?: number
|
|
83
|
+
): Promise<SendMessageBatchResultEntry[]> {
|
|
62
84
|
const endIndex = startIndex + this.batchSize;
|
|
63
85
|
const batch = messages.slice(startIndex, endIndex);
|
|
64
86
|
const params = {
|
|
@@ -66,20 +88,31 @@ export class Producer {
|
|
|
66
88
|
Entries: batch.map(toEntry)
|
|
67
89
|
};
|
|
68
90
|
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
const
|
|
91
|
+
const command = new SendMessageBatchCommand(params);
|
|
92
|
+
const result = await this.sqs.send(command);
|
|
93
|
+
const failedMessagesBatch = failedMessages.concat(
|
|
94
|
+
result.Failed.map((entry) => entry.Id)
|
|
95
|
+
);
|
|
96
|
+
const successfulMessagesBatch = successfulMessages.concat(
|
|
97
|
+
result.Successful
|
|
98
|
+
);
|
|
72
99
|
|
|
73
100
|
if (endIndex < messages.length) {
|
|
74
|
-
return this.sendBatch(
|
|
101
|
+
return this.sendBatch(
|
|
102
|
+
failedMessagesBatch,
|
|
103
|
+
successfulMessagesBatch,
|
|
104
|
+
messages,
|
|
105
|
+
endIndex
|
|
106
|
+
);
|
|
75
107
|
}
|
|
76
108
|
|
|
77
109
|
if (failedMessagesBatch.length === 0) {
|
|
78
110
|
return successfulMessagesBatch;
|
|
79
111
|
}
|
|
80
|
-
throw new Error(
|
|
112
|
+
throw new Error(
|
|
113
|
+
`Failed to send messages: ${failedMessagesBatch.join(', ')}`
|
|
114
|
+
);
|
|
81
115
|
}
|
|
82
|
-
|
|
83
116
|
}
|
|
84
117
|
|
|
85
118
|
Producer.create = (options: ProducerOptions): Producer => {
|
package/src/types.ts
CHANGED
|
@@ -1,88 +1,101 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
SendMessageBatchRequestEntry,
|
|
3
|
+
MessageAttributeValue
|
|
4
|
+
} from '@aws-sdk/client-sqs';
|
|
5
|
+
import { isObject, isString, isMessageAttributeValid } from './validation';
|
|
4
6
|
|
|
5
7
|
export interface Message {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
id: string;
|
|
9
|
+
body: string;
|
|
10
|
+
groupId?: string;
|
|
11
|
+
deduplicationId?: string;
|
|
12
|
+
delaySeconds?: number;
|
|
13
|
+
messageAttributes?: { [key: string]: MessageAttributeValue };
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
function entryFromObject(message: Message): SendMessageBatchRequestEntry {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
if (!message.body) {
|
|
18
|
+
throw new Error(`Object messages must have 'body' prop`);
|
|
19
|
+
}
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
if (!message.groupId && !message.deduplicationId && !message.id) {
|
|
22
|
+
throw new Error(`Object messages must have 'id' prop`);
|
|
23
|
+
}
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
if (message.deduplicationId && !message.groupId) {
|
|
26
|
+
throw new Error(`FIFO Queue messages must have 'groupId' prop`);
|
|
27
|
+
}
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
if (message.id) {
|
|
30
|
+
if (!isString(message.id)) {
|
|
31
|
+
throw new Error('Message.id value must be a string');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const entry: SendMessageBatchRequestEntry = {
|
|
36
|
+
Id: message.id,
|
|
37
|
+
MessageBody: message.body
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
if (message.delaySeconds) {
|
|
41
|
+
if (
|
|
42
|
+
typeof message.delaySeconds !== 'number' ||
|
|
43
|
+
message.delaySeconds < 0 ||
|
|
44
|
+
message.delaySeconds > 900
|
|
45
|
+
) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
'Message.delaySeconds value must be a number contained within [0 - 900]'
|
|
48
|
+
);
|
|
31
49
|
}
|
|
32
50
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
MessageBody: message.body
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
if (message.delaySeconds) {
|
|
39
|
-
if ((typeof message.delaySeconds !== 'number') ||
|
|
40
|
-
(message.delaySeconds < 0 || message.delaySeconds > 900)) {
|
|
41
|
-
throw new Error('Message.delaySeconds value must be a number contained within [0 - 900]');
|
|
42
|
-
}
|
|
51
|
+
entry.DelaySeconds = message.delaySeconds;
|
|
52
|
+
}
|
|
43
53
|
|
|
44
|
-
|
|
54
|
+
if (message.messageAttributes) {
|
|
55
|
+
if (!isObject(message.messageAttributes)) {
|
|
56
|
+
throw new Error('Message.messageAttributes must be an object');
|
|
45
57
|
}
|
|
46
58
|
|
|
47
|
-
|
|
48
|
-
if (!isObject(message.messageAttributes)) {
|
|
49
|
-
throw new Error('Message.messageAttributes must be an object');
|
|
50
|
-
}
|
|
59
|
+
Object.values(message.messageAttributes).every(isMessageAttributeValid);
|
|
51
60
|
|
|
52
|
-
|
|
61
|
+
entry.MessageAttributes = message.messageAttributes;
|
|
62
|
+
}
|
|
53
63
|
|
|
54
|
-
|
|
64
|
+
if (message.groupId) {
|
|
65
|
+
if (!isString(message.groupId)) {
|
|
66
|
+
throw new Error('Message.groupId value must be a string');
|
|
55
67
|
}
|
|
56
68
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
throw new Error('Message.groupId value must be a string');
|
|
60
|
-
}
|
|
69
|
+
entry.MessageGroupId = message.groupId;
|
|
70
|
+
}
|
|
61
71
|
|
|
62
|
-
|
|
72
|
+
if (message.deduplicationId) {
|
|
73
|
+
if (!isString(message.deduplicationId)) {
|
|
74
|
+
throw new Error('Message.deduplicationId value must be a string');
|
|
63
75
|
}
|
|
64
76
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
throw new Error('Message.deduplicationId value must be a string');
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
entry.MessageDeduplicationId = message.deduplicationId;
|
|
71
|
-
}
|
|
77
|
+
entry.MessageDeduplicationId = message.deduplicationId;
|
|
78
|
+
}
|
|
72
79
|
|
|
73
|
-
|
|
80
|
+
return entry;
|
|
74
81
|
}
|
|
75
82
|
|
|
76
83
|
function entryFromString(message: string): SendMessageBatchRequestEntry {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
return {
|
|
85
|
+
Id: message,
|
|
86
|
+
MessageBody: message
|
|
87
|
+
};
|
|
81
88
|
}
|
|
82
89
|
|
|
83
|
-
export function toEntry(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
export function toEntry(
|
|
91
|
+
message: string | Message
|
|
92
|
+
): SendMessageBatchRequestEntry {
|
|
93
|
+
if (isString(message)) {
|
|
94
|
+
return entryFromString(message as string);
|
|
95
|
+
}
|
|
96
|
+
if (isObject(message)) {
|
|
97
|
+
return entryFromObject(message as Message);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
throw new Error('A message can either be an object or a string');
|
|
88
101
|
}
|
package/src/validation.ts
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
|
|
2
1
|
export function isString(value: any): boolean {
|
|
3
|
-
|
|
2
|
+
return typeof value === 'string' || value instanceof String;
|
|
4
3
|
}
|
|
5
4
|
|
|
6
5
|
export function isObject(value: any): boolean {
|
|
7
|
-
|
|
6
|
+
return value && typeof value === 'object' && value instanceof Object;
|
|
8
7
|
}
|
|
9
8
|
|
|
10
9
|
export function isMessageAttributeValid(messageAttribute: any): boolean {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
if (!messageAttribute.DataType) {
|
|
11
|
+
throw new Error('A MessageAttribute must have a DataType key');
|
|
12
|
+
}
|
|
13
|
+
if (!isString(messageAttribute.DataType)) {
|
|
14
|
+
throw new Error('The DataType key of a MessageAttribute must be a String');
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
18
17
|
}
|