sqs-consumer 6.0.2 → 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/src/consumer.ts CHANGED
@@ -16,132 +16,42 @@ import {
16
16
  ReceiveMessageCommandOutput
17
17
  } from '@aws-sdk/client-sqs';
18
18
  import Debug from 'debug';
19
- import { EventEmitter } from 'events';
20
19
 
21
- import { AWSError } from './types';
20
+ import { ConsumerOptions, TypedEventEmitter } from './types';
22
21
  import { autoBind } from './bind';
23
- import { SQSError, TimeoutError } from './errors';
22
+ import {
23
+ SQSError,
24
+ TimeoutError,
25
+ toSQSError,
26
+ isConnectionError
27
+ } from './errors';
28
+ import { assertOptions, hasMessages } from './validation';
24
29
 
25
30
  const debug = Debug('sqs-consumer');
26
31
 
27
- const requiredOptions = [
28
- 'queueUrl',
29
- // only one of handleMessage / handleMessagesBatch is required
30
- 'handleMessage|handleMessageBatch'
31
- ];
32
-
33
- interface TimeoutResponse {
34
- timeout: NodeJS.Timeout;
35
- pending: Promise<void>;
36
- }
37
-
38
- function createTimeout(duration: number): TimeoutResponse[] {
39
- let timeout;
40
- const pending = new Promise((_, reject) => {
41
- timeout = setTimeout((): void => {
42
- reject(new TimeoutError());
43
- }, duration);
44
- });
45
- return [timeout, pending];
46
- }
47
-
48
- function assertOptions(options: ConsumerOptions): void {
49
- requiredOptions.forEach((option) => {
50
- const possibilities = option.split('|');
51
- if (!possibilities.find((p) => options[p])) {
52
- throw new Error(
53
- `Missing SQS consumer option [ ${possibilities.join(' or ')} ].`
54
- );
55
- }
56
- });
57
-
58
- if (options.batchSize > 10 || options.batchSize < 1) {
59
- throw new Error('SQS batchSize option must be between 1 and 10.');
60
- }
61
-
62
- if (
63
- options.heartbeatInterval &&
64
- !(options.heartbeatInterval < options.visibilityTimeout)
65
- ) {
66
- throw new Error('heartbeatInterval must be less than visibilityTimeout.');
67
- }
68
- }
69
-
70
- function isConnectionError(err: Error): boolean {
71
- if (err instanceof SQSError) {
72
- return (
73
- err.statusCode === 403 ||
74
- err.code === 'CredentialsError' ||
75
- err.code === 'UnknownEndpoint'
76
- );
77
- }
78
- return false;
79
- }
80
-
81
- function toSQSError(err: AWSError, message: string): SQSError {
82
- const sqsError = new SQSError(message);
83
- sqsError.code = err.name;
84
- sqsError.statusCode = err.$metadata?.httpStatusCode;
85
- sqsError.retryable = err.$retryable?.throttling;
86
- sqsError.service = err.$service;
87
- sqsError.fault = err.$fault;
88
- sqsError.time = new Date();
89
-
90
- return sqsError;
91
- }
92
-
93
- function hasMessages(response: ReceiveMessageCommandOutput): boolean {
94
- return response.Messages && response.Messages.length > 0;
95
- }
96
-
97
- export interface ConsumerOptions {
98
- queueUrl?: string;
99
- attributeNames?: string[];
100
- messageAttributeNames?: string[];
101
- stopped?: boolean;
102
- batchSize?: number;
103
- visibilityTimeout?: number;
104
- waitTimeSeconds?: number;
105
- authenticationErrorTimeout?: number;
106
- pollingWaitTimeMs?: number;
107
- terminateVisibilityTimeout?: boolean;
108
- heartbeatInterval?: number;
109
- sqs?: SQSClient;
110
- region?: string;
111
- handleMessageTimeout?: number;
112
- shouldDeleteMessages?: boolean;
113
- handleMessage?(message: Message): Promise<void>;
114
- handleMessageBatch?(messages: Message[]): Promise<void>;
115
- }
116
-
117
- interface Events {
118
- response_processed: [];
119
- empty: [];
120
- message_received: [Message];
121
- message_processed: [Message];
122
- error: [Error, void | Message | Message[]];
123
- timeout_error: [Error, Message];
124
- processing_error: [Error, Message];
125
- stopped: [];
126
- }
127
-
128
- export class Consumer extends EventEmitter {
32
+ /**
33
+ * [Usage](https://bbc.github.io/sqs-consumer/index.html#usage)
34
+ */
35
+ export class Consumer extends TypedEventEmitter {
36
+ private pollingTimeoutId: NodeJS.Timeout | undefined = undefined;
37
+ private heartbeatTimeoutId: NodeJS.Timeout | undefined = undefined;
38
+ private handleMessageTimeoutId: NodeJS.Timeout | undefined = undefined;
39
+ private stopped = true;
129
40
  private queueUrl: string;
130
- private handleMessage: (message: Message) => Promise<void>;
131
- private handleMessageBatch: (message: Message[]) => Promise<void>;
41
+ private handleMessage: (message: Message) => Promise<Message | void>;
42
+ private handleMessageBatch: (message: Message[]) => Promise<Message[] | void>;
43
+ private sqs: SQSClient;
132
44
  private handleMessageTimeout: number;
133
45
  private attributeNames: string[];
134
46
  private messageAttributeNames: string[];
135
- private stopped: boolean;
47
+ private shouldDeleteMessages: boolean;
136
48
  private batchSize: number;
137
49
  private visibilityTimeout: number;
50
+ private terminateVisibilityTimeout: boolean;
138
51
  private waitTimeSeconds: number;
139
52
  private authenticationErrorTimeout: number;
140
53
  private pollingWaitTimeMs: number;
141
- private terminateVisibilityTimeout: boolean;
142
54
  private heartbeatInterval: number;
143
- private sqs: SQSClient;
144
- private shouldDeleteMessages: boolean;
145
55
 
146
56
  constructor(options: ConsumerOptions) {
147
57
  super();
@@ -152,7 +62,6 @@ export class Consumer extends EventEmitter {
152
62
  this.handleMessageTimeout = options.handleMessageTimeout;
153
63
  this.attributeNames = options.attributeNames || [];
154
64
  this.messageAttributeNames = options.messageAttributeNames || [];
155
- this.stopped = true;
156
65
  this.batchSize = options.batchSize || 1;
157
66
  this.visibilityTimeout = options.visibilityTimeout;
158
67
  this.terminateVisibilityTimeout =
@@ -163,89 +72,171 @@ export class Consumer extends EventEmitter {
163
72
  options.authenticationErrorTimeout ?? 10000;
164
73
  this.pollingWaitTimeMs = options.pollingWaitTimeMs ?? 0;
165
74
  this.shouldDeleteMessages = options.shouldDeleteMessages ?? true;
166
-
167
75
  this.sqs =
168
76
  options.sqs ||
169
77
  new SQSClient({
170
78
  region: options.region || process.env.AWS_REGION || 'eu-west-1'
171
79
  });
172
-
173
80
  autoBind(this);
174
81
  }
175
82
 
176
- emit<T extends keyof Events>(event: T, ...args: Events[T]) {
177
- return super.emit(event, ...args);
83
+ /**
84
+ * Creates a new SQS consumer.
85
+ */
86
+ public static create(options: ConsumerOptions): Consumer {
87
+ return new Consumer(options);
178
88
  }
179
89
 
180
- on<T extends keyof Events>(
181
- event: T,
182
- listener: (...args: Events[T]) => void
183
- ): this {
184
- return super.on(event, listener);
90
+ /**
91
+ * Start polling the queue for messages.
92
+ */
93
+ public start(): void {
94
+ if (this.stopped) {
95
+ debug('Starting consumer');
96
+ this.stopped = false;
97
+ this.poll();
98
+ }
185
99
  }
186
100
 
187
- once<T extends keyof Events>(
188
- event: T,
189
- listener: (...args: Events[T]) => void
190
- ): this {
191
- return super.once(event, listener);
101
+ /**
102
+ * Stop polling the queue for messages (pre existing requests will still be made until concluded).
103
+ */
104
+ public stop(): void {
105
+ if (this.stopped) {
106
+ debug('Consumer was already stopped');
107
+ return;
108
+ }
109
+
110
+ debug('Stopping consumer');
111
+ this.stopped = true;
112
+
113
+ if (this.pollingTimeoutId) {
114
+ clearTimeout(this.pollingTimeoutId);
115
+ this.pollingTimeoutId = undefined;
116
+ }
117
+
118
+ this.emit('stopped');
192
119
  }
193
120
 
121
+ /**
122
+ * Returns the current polling state of the consumer: `true` if it is actively polling, `false` if it is not.
123
+ */
194
124
  public get isRunning(): boolean {
195
125
  return !this.stopped;
196
126
  }
197
127
 
198
- public static create(options: ConsumerOptions): Consumer {
199
- return new Consumer(options);
128
+ /**
129
+ * Emit one of the consumer's error events depending on the error received.
130
+ * @param err The error object to forward on
131
+ * @param message The message that the error occurred on
132
+ */
133
+ private emitError(err: Error, message?: Message): void {
134
+ if (!message) {
135
+ this.emit('error', err);
136
+ } else if (err.name === SQSError.name) {
137
+ this.emit('error', err, message);
138
+ } else if (err instanceof TimeoutError) {
139
+ this.emit('timeout_error', err, message);
140
+ } else {
141
+ this.emit('processing_error', err, message);
142
+ }
200
143
  }
201
144
 
202
- public start(): void {
145
+ /**
146
+ * Poll for new messages from SQS
147
+ */
148
+ private poll(): void {
203
149
  if (this.stopped) {
204
- debug('Starting consumer');
205
- this.stopped = false;
206
- this.poll();
150
+ debug('Poll was called while consumer was stopped, cancelling poll...');
151
+ return;
207
152
  }
153
+
154
+ debug('Polling for messages');
155
+
156
+ let currentPollingTimeout = this.pollingWaitTimeMs;
157
+ this.receiveMessage({
158
+ QueueUrl: this.queueUrl,
159
+ AttributeNames: this.attributeNames,
160
+ MessageAttributeNames: this.messageAttributeNames,
161
+ MaxNumberOfMessages: this.batchSize,
162
+ WaitTimeSeconds: this.waitTimeSeconds,
163
+ VisibilityTimeout: this.visibilityTimeout
164
+ })
165
+ .then(this.handleSqsResponse)
166
+ .catch((err) => {
167
+ this.emitError(err);
168
+ if (isConnectionError(err)) {
169
+ debug('There was an authentication error. Pausing before retrying.');
170
+ currentPollingTimeout = this.authenticationErrorTimeout;
171
+ }
172
+ return;
173
+ })
174
+ .then(() => {
175
+ if (this.pollingTimeoutId) {
176
+ clearTimeout(this.pollingTimeoutId);
177
+ }
178
+ this.pollingTimeoutId = setTimeout(this.poll, currentPollingTimeout);
179
+ })
180
+ .catch((err) => {
181
+ this.emitError(err);
182
+ });
208
183
  }
209
184
 
210
- public stop(): void {
211
- debug('Stopping consumer');
212
- this.stopped = true;
185
+ /**
186
+ * Send a request to SQS to retrieve messages
187
+ * @param params The required params to receive messages from SQS
188
+ */
189
+ private async receiveMessage(
190
+ params: ReceiveMessageCommandInput
191
+ ): Promise<ReceiveMessageCommandOutput> {
192
+ try {
193
+ return await this.sqs.send(new ReceiveMessageCommand(params));
194
+ } catch (err) {
195
+ throw toSQSError(err, `SQS receive message failed: ${err.message}`);
196
+ }
213
197
  }
214
198
 
199
+ /**
200
+ * Handles the response from AWS SQS, determining if we should proceed to
201
+ * the message handler.
202
+ * @param response The output from AWS SQS
203
+ */
215
204
  private async handleSqsResponse(
216
205
  response: ReceiveMessageCommandOutput
217
206
  ): Promise<void> {
218
- debug('Received SQS response');
219
- debug(response);
220
-
221
- if (response) {
222
- if (hasMessages(response)) {
223
- if (this.handleMessageBatch) {
224
- // prefer handling messages in batch when available
225
- await this.processMessageBatch(response.Messages);
226
- } else {
227
- await Promise.all(response.Messages.map(this.processMessage));
228
- }
229
- this.emit('response_processed');
207
+ if (hasMessages(response)) {
208
+ if (this.handleMessageBatch) {
209
+ await this.processMessageBatch(response.Messages);
230
210
  } else {
231
- this.emit('empty');
211
+ await Promise.all(response.Messages.map(this.processMessage));
232
212
  }
213
+
214
+ this.emit('response_processed');
215
+ } else if (response) {
216
+ this.emit('empty');
233
217
  }
234
218
  }
235
219
 
220
+ /**
221
+ * Process a message that has been received from SQS. This will execute the message
222
+ * handler and delete the message once complete.
223
+ * @param message The message that was delivered from SQS
224
+ */
236
225
  private async processMessage(message: Message): Promise<void> {
237
- this.emit('message_received', message);
238
-
239
- let heartbeat;
240
226
  try {
227
+ this.emit('message_received', message);
228
+
241
229
  if (this.heartbeatInterval) {
242
- heartbeat = this.startHeartbeat(async () => {
243
- return this.changeVisibilityTimeout(message, this.visibilityTimeout);
244
- });
230
+ this.heartbeatTimeoutId = this.startHeartbeat(message);
231
+ }
232
+
233
+ const ackedMessage = await this.executeHandler(message);
234
+
235
+ if (ackedMessage?.MessageId === message.MessageId) {
236
+ await this.deleteMessage(message);
237
+
238
+ this.emit('message_processed', message);
245
239
  }
246
- await this.executeHandler(message);
247
- await this.deleteMessage(message);
248
- this.emit('message_processed', message);
249
240
  } catch (err) {
250
241
  this.emitError(err, message);
251
242
 
@@ -253,63 +244,71 @@ export class Consumer extends EventEmitter {
253
244
  await this.changeVisibilityTimeout(message, 0);
254
245
  }
255
246
  } finally {
256
- clearInterval(heartbeat);
247
+ clearInterval(this.heartbeatTimeoutId);
248
+ this.heartbeatTimeoutId = undefined;
257
249
  }
258
250
  }
259
251
 
260
- private async receiveMessage(
261
- params: ReceiveMessageCommandInput
262
- ): Promise<ReceiveMessageCommandOutput> {
252
+ /**
253
+ * Process a batch of messages from the SQS queue.
254
+ * @param messages The messages that were delivered from SQS
255
+ */
256
+ private async processMessageBatch(messages: Message[]): Promise<void> {
263
257
  try {
264
- return await this.sqs.send(new ReceiveMessageCommand(params));
265
- } catch (err) {
266
- throw toSQSError(err, `SQS receive message failed: ${err.message}`);
267
- }
268
- }
258
+ messages.forEach((message) => {
259
+ this.emit('message_received', message);
260
+ });
269
261
 
270
- private async deleteMessage(message: Message): Promise<void> {
271
- if (!this.shouldDeleteMessages) {
272
- debug(
273
- 'Skipping message delete since shouldDeleteMessages is set to false'
274
- );
275
- return;
276
- }
277
- debug('Deleting message %s', message.MessageId);
262
+ if (this.heartbeatInterval) {
263
+ this.heartbeatTimeoutId = this.startHeartbeat(null, messages);
264
+ }
278
265
 
279
- const deleteParams: DeleteMessageCommandInput = {
280
- QueueUrl: this.queueUrl,
281
- ReceiptHandle: message.ReceiptHandle
282
- };
266
+ const ackedMessages = await this.executeBatchHandler(messages);
283
267
 
284
- try {
285
- await this.sqs.send(new DeleteMessageCommand(deleteParams));
286
- } catch (err) {
287
- throw toSQSError(err, `SQS delete message failed: ${err.message}`);
288
- }
289
- }
268
+ if (ackedMessages?.length > 0) {
269
+ await this.deleteMessageBatch(ackedMessages);
290
270
 
291
- private async executeHandler(message: Message): Promise<void> {
292
- let timeout;
293
- let pending;
294
- try {
295
- if (this.handleMessageTimeout) {
296
- [timeout, pending] = createTimeout(this.handleMessageTimeout);
297
- await Promise.race([this.handleMessage(message), pending]);
298
- } else {
299
- await this.handleMessage(message);
271
+ ackedMessages.forEach((message) => {
272
+ this.emit('message_processed', message);
273
+ });
300
274
  }
301
275
  } catch (err) {
302
- if (err instanceof TimeoutError) {
303
- err.message = `Message handler timed out after ${this.handleMessageTimeout}ms: Operation timed out.`;
304
- } else if (err instanceof Error) {
305
- err.message = `Unexpected message handler failure: ${err.message}`;
276
+ this.emit('error', err, messages);
277
+
278
+ if (this.terminateVisibilityTimeout) {
279
+ await this.changeVisibilityTimeoutBatch(messages, 0);
306
280
  }
307
- throw err;
308
281
  } finally {
309
- clearTimeout(timeout);
282
+ clearInterval(this.heartbeatTimeoutId);
283
+ this.heartbeatTimeoutId = undefined;
310
284
  }
311
285
  }
312
286
 
287
+ /**
288
+ * Trigger a function on a set interval
289
+ * @param heartbeatFn The function that should be triggered
290
+ */
291
+ private startHeartbeat(
292
+ message?: Message,
293
+ messages?: Message[]
294
+ ): NodeJS.Timeout {
295
+ return setInterval(() => {
296
+ if (this.handleMessageBatch) {
297
+ return this.changeVisibilityTimeoutBatch(
298
+ messages,
299
+ this.visibilityTimeout
300
+ );
301
+ } else {
302
+ return this.changeVisibilityTimeout(message, this.visibilityTimeout);
303
+ }
304
+ }, this.heartbeatInterval * 1000);
305
+ }
306
+
307
+ /**
308
+ * Change the visibility timeout on a message
309
+ * @param message The message to change the value of
310
+ * @param timeout The new timeout that should be set
311
+ */
313
312
  private async changeVisibilityTimeout(
314
313
  message: Message,
315
314
  timeout: number
@@ -330,82 +329,113 @@ export class Consumer extends EventEmitter {
330
329
  }
331
330
  }
332
331
 
333
- private emitError(err: Error, message: Message): void {
334
- if (err.name === SQSError.name) {
335
- this.emit('error', err, message);
336
- } else if (err instanceof TimeoutError) {
337
- this.emit('timeout_error', err, message);
338
- } else {
339
- this.emit('processing_error', err, message);
332
+ /**
333
+ * Change the visibility timeout on a batch of messages
334
+ * @param messages The messages to change the value of
335
+ * @param timeout The new timeout that should be set
336
+ */
337
+ private async changeVisibilityTimeoutBatch(
338
+ messages: Message[],
339
+ timeout: number
340
+ ): Promise<ChangeMessageVisibilityBatchCommandOutput> {
341
+ const params: ChangeMessageVisibilityBatchCommandInput = {
342
+ QueueUrl: this.queueUrl,
343
+ Entries: messages.map((message) => ({
344
+ Id: message.MessageId,
345
+ ReceiptHandle: message.ReceiptHandle,
346
+ VisibilityTimeout: timeout
347
+ }))
348
+ };
349
+ try {
350
+ return await this.sqs.send(
351
+ new ChangeMessageVisibilityBatchCommand(params)
352
+ );
353
+ } catch (err) {
354
+ this.emit(
355
+ 'error',
356
+ toSQSError(err, `Error changing visibility timeout: ${err.message}`),
357
+ messages
358
+ );
340
359
  }
341
360
  }
342
361
 
343
- private poll(): void {
344
- if (this.stopped) {
345
- this.emit('stopped');
346
- return;
362
+ /**
363
+ * Trigger the applications handleMessage function
364
+ * @param message The message that was received from SQS
365
+ */
366
+ private async executeHandler(message: Message): Promise<Message> {
367
+ try {
368
+ let result;
369
+
370
+ if (this.handleMessageTimeout) {
371
+ const pending = new Promise((_, reject) => {
372
+ this.handleMessageTimeoutId = setTimeout((): void => {
373
+ reject(new TimeoutError());
374
+ }, this.handleMessageTimeout);
375
+ });
376
+ result = await Promise.race([this.handleMessage(message), pending]);
377
+ } else {
378
+ result = await this.handleMessage(message);
379
+ }
380
+
381
+ return result instanceof Object ? result : message;
382
+ } catch (err) {
383
+ err.message =
384
+ err instanceof TimeoutError
385
+ ? `Message handler timed out after ${this.handleMessageTimeout}ms: Operation timed out.`
386
+ : `Unexpected message handler failure: ${err.message}`;
387
+ throw err;
388
+ } finally {
389
+ if (this.handleMessageTimeoutId) {
390
+ clearTimeout(this.handleMessageTimeoutId);
391
+ }
347
392
  }
393
+ }
348
394
 
349
- debug('Polling for messages');
350
- const receiveParams: ReceiveMessageCommandInput = {
351
- QueueUrl: this.queueUrl,
352
- AttributeNames: this.attributeNames,
353
- MessageAttributeNames: this.messageAttributeNames,
354
- MaxNumberOfMessages: this.batchSize,
355
- WaitTimeSeconds: this.waitTimeSeconds,
356
- VisibilityTimeout: this.visibilityTimeout
357
- };
395
+ /**
396
+ * Execute the application's message batch handler
397
+ * @param messages The messages that should be forwarded from the SQS queue
398
+ */
399
+ private async executeBatchHandler(messages: Message[]): Promise<Message[]> {
400
+ try {
401
+ const result = await this.handleMessageBatch(messages);
358
402
 
359
- let currentPollingTimeout = this.pollingWaitTimeMs;
360
- this.receiveMessage(receiveParams)
361
- .then(this.handleSqsResponse)
362
- .catch((err) => {
363
- this.emit('error', err);
364
- if (isConnectionError(err)) {
365
- debug('There was an authentication error. Pausing before retrying.');
366
- currentPollingTimeout = this.authenticationErrorTimeout;
367
- }
368
- return;
369
- })
370
- .then(() => {
371
- setTimeout(this.poll, currentPollingTimeout);
372
- })
373
- .catch((err) => {
374
- this.emit('error', err);
375
- });
403
+ return result instanceof Object ? result : messages;
404
+ } catch (err) {
405
+ err.message = `Unexpected message handler failure: ${err.message}`;
406
+ throw err;
407
+ }
376
408
  }
377
409
 
378
- private async processMessageBatch(messages: Message[]): Promise<void> {
379
- messages.forEach((message) => {
380
- this.emit('message_received', message);
381
- });
410
+ /**
411
+ * Delete a single message from SQS
412
+ * @param message The message to delete from the SQS queue
413
+ */
414
+ private async deleteMessage(message: Message): Promise<void> {
415
+ if (!this.shouldDeleteMessages) {
416
+ debug(
417
+ 'Skipping message delete since shouldDeleteMessages is set to false'
418
+ );
419
+ return;
420
+ }
421
+ debug('Deleting message %s', message.MessageId);
422
+
423
+ const deleteParams: DeleteMessageCommandInput = {
424
+ QueueUrl: this.queueUrl,
425
+ ReceiptHandle: message.ReceiptHandle
426
+ };
382
427
 
383
- let heartbeat;
384
428
  try {
385
- if (this.heartbeatInterval) {
386
- heartbeat = this.startHeartbeat(async () => {
387
- return this.changeVisibilityTimeoutBatch(
388
- messages,
389
- this.visibilityTimeout
390
- );
391
- });
392
- }
393
- await this.executeBatchHandler(messages);
394
- await this.deleteMessageBatch(messages);
395
- messages.forEach((message) => {
396
- this.emit('message_processed', message);
397
- });
429
+ await this.sqs.send(new DeleteMessageCommand(deleteParams));
398
430
  } catch (err) {
399
- this.emit('error', err, messages);
400
-
401
- if (this.terminateVisibilityTimeout) {
402
- await this.changeVisibilityTimeoutBatch(messages, 0);
403
- }
404
- } finally {
405
- clearInterval(heartbeat);
431
+ throw toSQSError(err, `SQS delete message failed: ${err.message}`);
406
432
  }
407
433
  }
408
434
 
435
+ /**
436
+ * Delete a batch of messages from the SQS queue.
437
+ * @param messages The messages that should be deleted from SQS
438
+ */
409
439
  private async deleteMessageBatch(messages: Message[]): Promise<void> {
410
440
  if (!this.shouldDeleteMessages) {
411
441
  debug(
@@ -432,44 +462,4 @@ export class Consumer extends EventEmitter {
432
462
  throw toSQSError(err, `SQS delete message failed: ${err.message}`);
433
463
  }
434
464
  }
435
-
436
- private async executeBatchHandler(messages: Message[]): Promise<void> {
437
- try {
438
- await this.handleMessageBatch(messages);
439
- } catch (err) {
440
- err.message = `Unexpected message handler failure: ${err.message}`;
441
- throw err;
442
- }
443
- }
444
-
445
- private async changeVisibilityTimeoutBatch(
446
- messages: Message[],
447
- timeout: number
448
- ): Promise<ChangeMessageVisibilityBatchCommandOutput> {
449
- const params: ChangeMessageVisibilityBatchCommandInput = {
450
- QueueUrl: this.queueUrl,
451
- Entries: messages.map((message) => ({
452
- Id: message.MessageId,
453
- ReceiptHandle: message.ReceiptHandle,
454
- VisibilityTimeout: timeout
455
- }))
456
- };
457
- try {
458
- return await this.sqs.send(
459
- new ChangeMessageVisibilityBatchCommand(params)
460
- );
461
- } catch (err) {
462
- this.emit(
463
- 'error',
464
- toSQSError(err, `Error changing visibility timeout: ${err.message}`),
465
- messages
466
- );
467
- }
468
- }
469
-
470
- private startHeartbeat(heartbeatFn: () => void): NodeJS.Timeout {
471
- return setInterval(() => {
472
- heartbeatFn();
473
- }, this.heartbeatInterval * 1000);
474
- }
475
465
  }