sqs-consumer 6.1.0 → 6.2.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/.github/workflows/coverage.yml +1 -1
- package/.github/workflows/docs.yml +55 -0
- package/.prettierignore +2 -1
- package/README.md +18 -42
- package/dist/bind.d.ts +4 -0
- package/dist/bind.js +9 -0
- package/dist/consumer.d.ts +90 -21
- package/dist/consumer.js +244 -217
- package/dist/errors.d.ts +13 -1
- package/dist/errors.js +32 -1
- package/dist/types.d.ts +134 -1
- package/dist/types.js +28 -0
- package/dist/validation.d.ts +13 -0
- package/dist/validation.js +36 -0
- package/package.json +13 -13
- package/src/bind.ts +9 -0
- package/src/consumer.ts +289 -279
- package/src/errors.ts +36 -1
- package/src/types.ts +146 -1
- package/src/validation.ts +45 -0
- package/typedoc.json +13 -0
package/dist/consumer.js
CHANGED
|
@@ -3,74 +3,29 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Consumer = void 0;
|
|
4
4
|
const client_sqs_1 = require("@aws-sdk/client-sqs");
|
|
5
5
|
const debug_1 = require("debug");
|
|
6
|
-
const
|
|
6
|
+
const types_1 = require("./types");
|
|
7
7
|
const bind_1 = require("./bind");
|
|
8
8
|
const errors_1 = require("./errors");
|
|
9
|
+
const validation_1 = require("./validation");
|
|
9
10
|
const debug = (0, debug_1.default)('sqs-consumer');
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
];
|
|
15
|
-
function createTimeout(duration) {
|
|
16
|
-
let timeout;
|
|
17
|
-
const pending = new Promise((_, reject) => {
|
|
18
|
-
timeout = setTimeout(() => {
|
|
19
|
-
reject(new errors_1.TimeoutError());
|
|
20
|
-
}, duration);
|
|
21
|
-
});
|
|
22
|
-
return [timeout, pending];
|
|
23
|
-
}
|
|
24
|
-
function assertOptions(options) {
|
|
25
|
-
requiredOptions.forEach((option) => {
|
|
26
|
-
const possibilities = option.split('|');
|
|
27
|
-
if (!possibilities.find((p) => options[p])) {
|
|
28
|
-
throw new Error(`Missing SQS consumer option [ ${possibilities.join(' or ')} ].`);
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
if (options.batchSize > 10 || options.batchSize < 1) {
|
|
32
|
-
throw new Error('SQS batchSize option must be between 1 and 10.');
|
|
33
|
-
}
|
|
34
|
-
if (options.heartbeatInterval &&
|
|
35
|
-
!(options.heartbeatInterval < options.visibilityTimeout)) {
|
|
36
|
-
throw new Error('heartbeatInterval must be less than visibilityTimeout.');
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
function isConnectionError(err) {
|
|
40
|
-
if (err instanceof errors_1.SQSError) {
|
|
41
|
-
return (err.statusCode === 403 ||
|
|
42
|
-
err.code === 'CredentialsError' ||
|
|
43
|
-
err.code === 'UnknownEndpoint' ||
|
|
44
|
-
err.code === 'AWS.SimpleQueueService.NonExistentQueue');
|
|
45
|
-
}
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
function toSQSError(err, message) {
|
|
49
|
-
var _a, _b;
|
|
50
|
-
const sqsError = new errors_1.SQSError(message);
|
|
51
|
-
sqsError.code = err.name;
|
|
52
|
-
sqsError.statusCode = (_a = err.$metadata) === null || _a === void 0 ? void 0 : _a.httpStatusCode;
|
|
53
|
-
sqsError.retryable = (_b = err.$retryable) === null || _b === void 0 ? void 0 : _b.throttling;
|
|
54
|
-
sqsError.service = err.$service;
|
|
55
|
-
sqsError.fault = err.$fault;
|
|
56
|
-
sqsError.time = new Date();
|
|
57
|
-
return sqsError;
|
|
58
|
-
}
|
|
59
|
-
function hasMessages(response) {
|
|
60
|
-
return response.Messages && response.Messages.length > 0;
|
|
61
|
-
}
|
|
62
|
-
class Consumer extends events_1.EventEmitter {
|
|
11
|
+
/**
|
|
12
|
+
* [Usage](https://bbc.github.io/sqs-consumer/index.html#usage)
|
|
13
|
+
*/
|
|
14
|
+
class Consumer extends types_1.TypedEventEmitter {
|
|
63
15
|
constructor(options) {
|
|
64
16
|
var _a, _b, _c, _d;
|
|
65
17
|
super();
|
|
66
|
-
|
|
18
|
+
this.pollingTimeoutId = undefined;
|
|
19
|
+
this.heartbeatTimeoutId = undefined;
|
|
20
|
+
this.handleMessageTimeoutId = undefined;
|
|
21
|
+
this.stopped = true;
|
|
22
|
+
(0, validation_1.assertOptions)(options);
|
|
67
23
|
this.queueUrl = options.queueUrl;
|
|
68
24
|
this.handleMessage = options.handleMessage;
|
|
69
25
|
this.handleMessageBatch = options.handleMessageBatch;
|
|
70
26
|
this.handleMessageTimeout = options.handleMessageTimeout;
|
|
71
27
|
this.attributeNames = options.attributeNames || [];
|
|
72
28
|
this.messageAttributeNames = options.messageAttributeNames || [];
|
|
73
|
-
this.stopped = true;
|
|
74
29
|
this.batchSize = options.batchSize || 1;
|
|
75
30
|
this.visibilityTimeout = options.visibilityTimeout;
|
|
76
31
|
this.terminateVisibilityTimeout =
|
|
@@ -88,21 +43,15 @@ class Consumer extends events_1.EventEmitter {
|
|
|
88
43
|
});
|
|
89
44
|
(0, bind_1.autoBind)(this);
|
|
90
45
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
on(event, listener) {
|
|
95
|
-
return super.on(event, listener);
|
|
96
|
-
}
|
|
97
|
-
once(event, listener) {
|
|
98
|
-
return super.once(event, listener);
|
|
99
|
-
}
|
|
100
|
-
get isRunning() {
|
|
101
|
-
return !this.stopped;
|
|
102
|
-
}
|
|
46
|
+
/**
|
|
47
|
+
* Creates a new SQS consumer.
|
|
48
|
+
*/
|
|
103
49
|
static create(options) {
|
|
104
50
|
return new Consumer(options);
|
|
105
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Start polling the queue for messages.
|
|
54
|
+
*/
|
|
106
55
|
start() {
|
|
107
56
|
if (this.stopped) {
|
|
108
57
|
debug('Starting consumer');
|
|
@@ -110,117 +59,38 @@ class Consumer extends events_1.EventEmitter {
|
|
|
110
59
|
this.poll();
|
|
111
60
|
}
|
|
112
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Stop polling the queue for messages (pre existing requests will still be made until concluded).
|
|
64
|
+
*/
|
|
113
65
|
stop() {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
async handleSqsResponse(response) {
|
|
118
|
-
debug('Received SQS response');
|
|
119
|
-
debug(response);
|
|
120
|
-
if (response) {
|
|
121
|
-
if (hasMessages(response)) {
|
|
122
|
-
if (this.handleMessageBatch) {
|
|
123
|
-
// prefer handling messages in batch when available
|
|
124
|
-
await this.processMessageBatch(response.Messages);
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
await Promise.all(response.Messages.map(this.processMessage));
|
|
128
|
-
}
|
|
129
|
-
this.emit('response_processed');
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
this.emit('empty');
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
async processMessage(message) {
|
|
137
|
-
this.emit('message_received', message);
|
|
138
|
-
let heartbeat;
|
|
139
|
-
try {
|
|
140
|
-
if (this.heartbeatInterval) {
|
|
141
|
-
heartbeat = this.startHeartbeat(async () => {
|
|
142
|
-
return this.changeVisibilityTimeout(message, this.visibilityTimeout);
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
await this.executeHandler(message);
|
|
146
|
-
await this.deleteMessage(message);
|
|
147
|
-
this.emit('message_processed', message);
|
|
148
|
-
}
|
|
149
|
-
catch (err) {
|
|
150
|
-
this.emitError(err, message);
|
|
151
|
-
if (this.terminateVisibilityTimeout) {
|
|
152
|
-
await this.changeVisibilityTimeout(message, 0);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
finally {
|
|
156
|
-
clearInterval(heartbeat);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
async receiveMessage(params) {
|
|
160
|
-
try {
|
|
161
|
-
return await this.sqs.send(new client_sqs_1.ReceiveMessageCommand(params));
|
|
162
|
-
}
|
|
163
|
-
catch (err) {
|
|
164
|
-
throw toSQSError(err, `SQS receive message failed: ${err.message}`);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
async deleteMessage(message) {
|
|
168
|
-
if (!this.shouldDeleteMessages) {
|
|
169
|
-
debug('Skipping message delete since shouldDeleteMessages is set to false');
|
|
66
|
+
if (this.stopped) {
|
|
67
|
+
debug('Consumer was already stopped');
|
|
170
68
|
return;
|
|
171
69
|
}
|
|
172
|
-
debug('
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
try {
|
|
178
|
-
await this.sqs.send(new client_sqs_1.DeleteMessageCommand(deleteParams));
|
|
179
|
-
}
|
|
180
|
-
catch (err) {
|
|
181
|
-
throw toSQSError(err, `SQS delete message failed: ${err.message}`);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
async executeHandler(message) {
|
|
185
|
-
let timeout;
|
|
186
|
-
let pending;
|
|
187
|
-
try {
|
|
188
|
-
if (this.handleMessageTimeout) {
|
|
189
|
-
[timeout, pending] = createTimeout(this.handleMessageTimeout);
|
|
190
|
-
await Promise.race([this.handleMessage(message), pending]);
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
await this.handleMessage(message);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
catch (err) {
|
|
197
|
-
if (err instanceof errors_1.TimeoutError) {
|
|
198
|
-
err.message = `Message handler timed out after ${this.handleMessageTimeout}ms: Operation timed out.`;
|
|
199
|
-
}
|
|
200
|
-
else if (err instanceof Error) {
|
|
201
|
-
err.message = `Unexpected message handler failure: ${err.message}`;
|
|
202
|
-
}
|
|
203
|
-
throw err;
|
|
204
|
-
}
|
|
205
|
-
finally {
|
|
206
|
-
clearTimeout(timeout);
|
|
70
|
+
debug('Stopping consumer');
|
|
71
|
+
this.stopped = true;
|
|
72
|
+
if (this.pollingTimeoutId) {
|
|
73
|
+
clearTimeout(this.pollingTimeoutId);
|
|
74
|
+
this.pollingTimeoutId = undefined;
|
|
207
75
|
}
|
|
76
|
+
this.emit('stopped');
|
|
208
77
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
VisibilityTimeout: timeout
|
|
215
|
-
};
|
|
216
|
-
return await this.sqs.send(new client_sqs_1.ChangeMessageVisibilityCommand(input));
|
|
217
|
-
}
|
|
218
|
-
catch (err) {
|
|
219
|
-
this.emit('error', toSQSError(err, `Error changing visibility timeout: ${err.message}`), message);
|
|
220
|
-
}
|
|
78
|
+
/**
|
|
79
|
+
* Returns the current polling state of the consumer: `true` if it is actively polling, `false` if it is not.
|
|
80
|
+
*/
|
|
81
|
+
get isRunning() {
|
|
82
|
+
return !this.stopped;
|
|
221
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Emit one of the consumer's error events depending on the error received.
|
|
86
|
+
* @param err The error object to forward on
|
|
87
|
+
* @param message The message that the error occurred on
|
|
88
|
+
*/
|
|
222
89
|
emitError(err, message) {
|
|
223
|
-
if (
|
|
90
|
+
if (!message) {
|
|
91
|
+
this.emit('error', err);
|
|
92
|
+
}
|
|
93
|
+
else if (err.name === errors_1.SQSError.name) {
|
|
224
94
|
this.emit('error', err, message);
|
|
225
95
|
}
|
|
226
96
|
else if (err instanceof errors_1.TimeoutError) {
|
|
@@ -230,56 +100,121 @@ class Consumer extends events_1.EventEmitter {
|
|
|
230
100
|
this.emit('processing_error', err, message);
|
|
231
101
|
}
|
|
232
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Poll for new messages from SQS
|
|
105
|
+
*/
|
|
233
106
|
poll() {
|
|
234
107
|
if (this.stopped) {
|
|
235
|
-
|
|
108
|
+
debug('Poll was called while consumer was stopped, cancelling poll...');
|
|
236
109
|
return;
|
|
237
110
|
}
|
|
238
111
|
debug('Polling for messages');
|
|
239
|
-
|
|
112
|
+
let currentPollingTimeout = this.pollingWaitTimeMs;
|
|
113
|
+
this.receiveMessage({
|
|
240
114
|
QueueUrl: this.queueUrl,
|
|
241
115
|
AttributeNames: this.attributeNames,
|
|
242
116
|
MessageAttributeNames: this.messageAttributeNames,
|
|
243
117
|
MaxNumberOfMessages: this.batchSize,
|
|
244
118
|
WaitTimeSeconds: this.waitTimeSeconds,
|
|
245
119
|
VisibilityTimeout: this.visibilityTimeout
|
|
246
|
-
}
|
|
247
|
-
let currentPollingTimeout = this.pollingWaitTimeMs;
|
|
248
|
-
this.receiveMessage(receiveParams)
|
|
120
|
+
})
|
|
249
121
|
.then(this.handleSqsResponse)
|
|
250
122
|
.catch((err) => {
|
|
251
|
-
this.
|
|
252
|
-
if (isConnectionError(err)) {
|
|
123
|
+
this.emitError(err);
|
|
124
|
+
if ((0, errors_1.isConnectionError)(err)) {
|
|
253
125
|
debug('There was an authentication error. Pausing before retrying.');
|
|
254
126
|
currentPollingTimeout = this.authenticationErrorTimeout;
|
|
255
127
|
}
|
|
256
128
|
return;
|
|
257
129
|
})
|
|
258
130
|
.then(() => {
|
|
259
|
-
|
|
131
|
+
if (this.pollingTimeoutId) {
|
|
132
|
+
clearTimeout(this.pollingTimeoutId);
|
|
133
|
+
}
|
|
134
|
+
this.pollingTimeoutId = setTimeout(this.poll, currentPollingTimeout);
|
|
260
135
|
})
|
|
261
136
|
.catch((err) => {
|
|
262
|
-
this.
|
|
137
|
+
this.emitError(err);
|
|
263
138
|
});
|
|
264
139
|
}
|
|
265
|
-
|
|
266
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Send a request to SQS to retrieve messages
|
|
142
|
+
* @param params The required params to receive messages from SQS
|
|
143
|
+
*/
|
|
144
|
+
async receiveMessage(params) {
|
|
145
|
+
try {
|
|
146
|
+
return await this.sqs.send(new client_sqs_1.ReceiveMessageCommand(params));
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
throw (0, errors_1.toSQSError)(err, `SQS receive message failed: ${err.message}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Handles the response from AWS SQS, determining if we should proceed to
|
|
154
|
+
* the message handler.
|
|
155
|
+
* @param response The output from AWS SQS
|
|
156
|
+
*/
|
|
157
|
+
async handleSqsResponse(response) {
|
|
158
|
+
if ((0, validation_1.hasMessages)(response)) {
|
|
159
|
+
if (this.handleMessageBatch) {
|
|
160
|
+
await this.processMessageBatch(response.Messages);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
await Promise.all(response.Messages.map(this.processMessage));
|
|
164
|
+
}
|
|
165
|
+
this.emit('response_processed');
|
|
166
|
+
}
|
|
167
|
+
else if (response) {
|
|
168
|
+
this.emit('empty');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Process a message that has been received from SQS. This will execute the message
|
|
173
|
+
* handler and delete the message once complete.
|
|
174
|
+
* @param message The message that was delivered from SQS
|
|
175
|
+
*/
|
|
176
|
+
async processMessage(message) {
|
|
177
|
+
try {
|
|
267
178
|
this.emit('message_received', message);
|
|
268
|
-
|
|
269
|
-
|
|
179
|
+
if (this.heartbeatInterval) {
|
|
180
|
+
this.heartbeatTimeoutId = this.startHeartbeat(message);
|
|
181
|
+
}
|
|
182
|
+
const ackedMessage = await this.executeHandler(message);
|
|
183
|
+
if ((ackedMessage === null || ackedMessage === void 0 ? void 0 : ackedMessage.MessageId) === message.MessageId) {
|
|
184
|
+
await this.deleteMessage(message);
|
|
185
|
+
this.emit('message_processed', message);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
this.emitError(err, message);
|
|
190
|
+
if (this.terminateVisibilityTimeout) {
|
|
191
|
+
await this.changeVisibilityTimeout(message, 0);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
clearInterval(this.heartbeatTimeoutId);
|
|
196
|
+
this.heartbeatTimeoutId = undefined;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Process a batch of messages from the SQS queue.
|
|
201
|
+
* @param messages The messages that were delivered from SQS
|
|
202
|
+
*/
|
|
203
|
+
async processMessageBatch(messages) {
|
|
270
204
|
try {
|
|
205
|
+
messages.forEach((message) => {
|
|
206
|
+
this.emit('message_received', message);
|
|
207
|
+
});
|
|
271
208
|
if (this.heartbeatInterval) {
|
|
272
|
-
|
|
273
|
-
return this.changeVisibilityTimeoutBatch(messages, this.visibilityTimeout);
|
|
274
|
-
});
|
|
209
|
+
this.heartbeatTimeoutId = this.startHeartbeat(null, messages);
|
|
275
210
|
}
|
|
276
211
|
const ackedMessages = await this.executeBatchHandler(messages);
|
|
277
|
-
if (ackedMessages.length > 0) {
|
|
212
|
+
if ((ackedMessages === null || ackedMessages === void 0 ? void 0 : ackedMessages.length) > 0) {
|
|
278
213
|
await this.deleteMessageBatch(ackedMessages);
|
|
214
|
+
ackedMessages.forEach((message) => {
|
|
215
|
+
this.emit('message_processed', message);
|
|
216
|
+
});
|
|
279
217
|
}
|
|
280
|
-
ackedMessages.forEach((message) => {
|
|
281
|
-
this.emit('message_processed', message);
|
|
282
|
-
});
|
|
283
218
|
}
|
|
284
219
|
catch (err) {
|
|
285
220
|
this.emit('error', err, messages);
|
|
@@ -288,62 +223,154 @@ class Consumer extends events_1.EventEmitter {
|
|
|
288
223
|
}
|
|
289
224
|
}
|
|
290
225
|
finally {
|
|
291
|
-
clearInterval(
|
|
226
|
+
clearInterval(this.heartbeatTimeoutId);
|
|
227
|
+
this.heartbeatTimeoutId = undefined;
|
|
292
228
|
}
|
|
293
229
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
230
|
+
/**
|
|
231
|
+
* Trigger a function on a set interval
|
|
232
|
+
* @param heartbeatFn The function that should be triggered
|
|
233
|
+
*/
|
|
234
|
+
startHeartbeat(message, messages) {
|
|
235
|
+
return setInterval(() => {
|
|
236
|
+
if (this.handleMessageBatch) {
|
|
237
|
+
return this.changeVisibilityTimeoutBatch(messages, this.visibilityTimeout);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
return this.changeVisibilityTimeout(message, this.visibilityTimeout);
|
|
241
|
+
}
|
|
242
|
+
}, this.heartbeatInterval * 1000);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Change the visibility timeout on a message
|
|
246
|
+
* @param message The message to change the value of
|
|
247
|
+
* @param timeout The new timeout that should be set
|
|
248
|
+
*/
|
|
249
|
+
async changeVisibilityTimeout(message, timeout) {
|
|
250
|
+
try {
|
|
251
|
+
const input = {
|
|
252
|
+
QueueUrl: this.queueUrl,
|
|
253
|
+
ReceiptHandle: message.ReceiptHandle,
|
|
254
|
+
VisibilityTimeout: timeout
|
|
255
|
+
};
|
|
256
|
+
return await this.sqs.send(new client_sqs_1.ChangeMessageVisibilityCommand(input));
|
|
298
257
|
}
|
|
299
|
-
|
|
300
|
-
|
|
258
|
+
catch (err) {
|
|
259
|
+
this.emit('error', (0, errors_1.toSQSError)(err, `Error changing visibility timeout: ${err.message}`), message);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Change the visibility timeout on a batch of messages
|
|
264
|
+
* @param messages The messages to change the value of
|
|
265
|
+
* @param timeout The new timeout that should be set
|
|
266
|
+
*/
|
|
267
|
+
async changeVisibilityTimeoutBatch(messages, timeout) {
|
|
268
|
+
const params = {
|
|
301
269
|
QueueUrl: this.queueUrl,
|
|
302
270
|
Entries: messages.map((message) => ({
|
|
303
271
|
Id: message.MessageId,
|
|
304
|
-
ReceiptHandle: message.ReceiptHandle
|
|
272
|
+
ReceiptHandle: message.ReceiptHandle,
|
|
273
|
+
VisibilityTimeout: timeout
|
|
305
274
|
}))
|
|
306
275
|
};
|
|
307
276
|
try {
|
|
308
|
-
await this.sqs.send(new client_sqs_1.
|
|
277
|
+
return await this.sqs.send(new client_sqs_1.ChangeMessageVisibilityBatchCommand(params));
|
|
309
278
|
}
|
|
310
279
|
catch (err) {
|
|
311
|
-
|
|
280
|
+
this.emit('error', (0, errors_1.toSQSError)(err, `Error changing visibility timeout: ${err.message}`), messages);
|
|
312
281
|
}
|
|
313
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* Trigger the applications handleMessage function
|
|
285
|
+
* @param message The message that was received from SQS
|
|
286
|
+
*/
|
|
287
|
+
async executeHandler(message) {
|
|
288
|
+
try {
|
|
289
|
+
let result;
|
|
290
|
+
if (this.handleMessageTimeout) {
|
|
291
|
+
const pending = new Promise((_, reject) => {
|
|
292
|
+
this.handleMessageTimeoutId = setTimeout(() => {
|
|
293
|
+
reject(new errors_1.TimeoutError());
|
|
294
|
+
}, this.handleMessageTimeout);
|
|
295
|
+
});
|
|
296
|
+
result = await Promise.race([this.handleMessage(message), pending]);
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
result = await this.handleMessage(message);
|
|
300
|
+
}
|
|
301
|
+
return result instanceof Object ? result : message;
|
|
302
|
+
}
|
|
303
|
+
catch (err) {
|
|
304
|
+
err.message =
|
|
305
|
+
err instanceof errors_1.TimeoutError
|
|
306
|
+
? `Message handler timed out after ${this.handleMessageTimeout}ms: Operation timed out.`
|
|
307
|
+
: `Unexpected message handler failure: ${err.message}`;
|
|
308
|
+
throw err;
|
|
309
|
+
}
|
|
310
|
+
finally {
|
|
311
|
+
if (this.handleMessageTimeoutId) {
|
|
312
|
+
clearTimeout(this.handleMessageTimeoutId);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Execute the application's message batch handler
|
|
318
|
+
* @param messages The messages that should be forwarded from the SQS queue
|
|
319
|
+
*/
|
|
314
320
|
async executeBatchHandler(messages) {
|
|
315
321
|
try {
|
|
316
322
|
const result = await this.handleMessageBatch(messages);
|
|
317
|
-
|
|
318
|
-
return result;
|
|
319
|
-
}
|
|
320
|
-
return messages;
|
|
323
|
+
return result instanceof Object ? result : messages;
|
|
321
324
|
}
|
|
322
325
|
catch (err) {
|
|
323
326
|
err.message = `Unexpected message handler failure: ${err.message}`;
|
|
324
327
|
throw err;
|
|
325
328
|
}
|
|
326
329
|
}
|
|
327
|
-
|
|
328
|
-
|
|
330
|
+
/**
|
|
331
|
+
* Delete a single message from SQS
|
|
332
|
+
* @param message The message to delete from the SQS queue
|
|
333
|
+
*/
|
|
334
|
+
async deleteMessage(message) {
|
|
335
|
+
if (!this.shouldDeleteMessages) {
|
|
336
|
+
debug('Skipping message delete since shouldDeleteMessages is set to false');
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
debug('Deleting message %s', message.MessageId);
|
|
340
|
+
const deleteParams = {
|
|
341
|
+
QueueUrl: this.queueUrl,
|
|
342
|
+
ReceiptHandle: message.ReceiptHandle
|
|
343
|
+
};
|
|
344
|
+
try {
|
|
345
|
+
await this.sqs.send(new client_sqs_1.DeleteMessageCommand(deleteParams));
|
|
346
|
+
}
|
|
347
|
+
catch (err) {
|
|
348
|
+
throw (0, errors_1.toSQSError)(err, `SQS delete message failed: ${err.message}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Delete a batch of messages from the SQS queue.
|
|
353
|
+
* @param messages The messages that should be deleted from SQS
|
|
354
|
+
*/
|
|
355
|
+
async deleteMessageBatch(messages) {
|
|
356
|
+
if (!this.shouldDeleteMessages) {
|
|
357
|
+
debug('Skipping message delete since shouldDeleteMessages is set to false');
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
debug('Deleting messages %s', messages.map((msg) => msg.MessageId).join(' ,'));
|
|
361
|
+
const deleteParams = {
|
|
329
362
|
QueueUrl: this.queueUrl,
|
|
330
363
|
Entries: messages.map((message) => ({
|
|
331
364
|
Id: message.MessageId,
|
|
332
|
-
ReceiptHandle: message.ReceiptHandle
|
|
333
|
-
VisibilityTimeout: timeout
|
|
365
|
+
ReceiptHandle: message.ReceiptHandle
|
|
334
366
|
}))
|
|
335
367
|
};
|
|
336
368
|
try {
|
|
337
|
-
|
|
369
|
+
await this.sqs.send(new client_sqs_1.DeleteMessageBatchCommand(deleteParams));
|
|
338
370
|
}
|
|
339
371
|
catch (err) {
|
|
340
|
-
|
|
372
|
+
throw (0, errors_1.toSQSError)(err, `SQS delete message failed: ${err.message}`);
|
|
341
373
|
}
|
|
342
374
|
}
|
|
343
|
-
startHeartbeat(heartbeatFn) {
|
|
344
|
-
return setInterval(() => {
|
|
345
|
-
heartbeatFn();
|
|
346
|
-
}, this.heartbeatInterval * 1000);
|
|
347
|
-
}
|
|
348
375
|
}
|
|
349
376
|
exports.Consumer = Consumer;
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AWSError } from './types';
|
|
1
2
|
declare class SQSError extends Error {
|
|
2
3
|
code: string;
|
|
3
4
|
statusCode: number;
|
|
@@ -10,4 +11,15 @@ declare class SQSError extends Error {
|
|
|
10
11
|
declare class TimeoutError extends Error {
|
|
11
12
|
constructor(message?: string);
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Checks if the error provided should be treated as a connection error.
|
|
16
|
+
* @param err The error that was received.
|
|
17
|
+
*/
|
|
18
|
+
declare function isConnectionError(err: Error): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Formats an AWSError the the SQSError type.
|
|
21
|
+
* @param err The error object that was received.
|
|
22
|
+
* @param message The message that the error occurred on.
|
|
23
|
+
*/
|
|
24
|
+
declare function toSQSError(err: AWSError, message: string): SQSError;
|
|
25
|
+
export { SQSError, TimeoutError, isConnectionError, toSQSError };
|
package/dist/errors.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TimeoutError = exports.SQSError = void 0;
|
|
3
|
+
exports.toSQSError = exports.isConnectionError = exports.TimeoutError = exports.SQSError = void 0;
|
|
4
4
|
class SQSError extends Error {
|
|
5
5
|
constructor(message) {
|
|
6
6
|
super(message);
|
|
@@ -16,3 +16,34 @@ class TimeoutError extends Error {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
exports.TimeoutError = TimeoutError;
|
|
19
|
+
/**
|
|
20
|
+
* Checks if the error provided should be treated as a connection error.
|
|
21
|
+
* @param err The error that was received.
|
|
22
|
+
*/
|
|
23
|
+
function isConnectionError(err) {
|
|
24
|
+
if (err instanceof SQSError) {
|
|
25
|
+
return (err.statusCode === 403 ||
|
|
26
|
+
err.code === 'CredentialsError' ||
|
|
27
|
+
err.code === 'UnknownEndpoint' ||
|
|
28
|
+
err.code === 'AWS.SimpleQueueService.NonExistentQueue');
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
exports.isConnectionError = isConnectionError;
|
|
33
|
+
/**
|
|
34
|
+
* Formats an AWSError the the SQSError type.
|
|
35
|
+
* @param err The error object that was received.
|
|
36
|
+
* @param message The message that the error occurred on.
|
|
37
|
+
*/
|
|
38
|
+
function toSQSError(err, message) {
|
|
39
|
+
var _a, _b;
|
|
40
|
+
const sqsError = new SQSError(message);
|
|
41
|
+
sqsError.code = err.name;
|
|
42
|
+
sqsError.statusCode = (_a = err.$metadata) === null || _a === void 0 ? void 0 : _a.httpStatusCode;
|
|
43
|
+
sqsError.retryable = (_b = err.$retryable) === null || _b === void 0 ? void 0 : _b.throttling;
|
|
44
|
+
sqsError.service = err.$service;
|
|
45
|
+
sqsError.fault = err.$fault;
|
|
46
|
+
sqsError.time = new Date();
|
|
47
|
+
return sqsError;
|
|
48
|
+
}
|
|
49
|
+
exports.toSQSError = toSQSError;
|