sqs-consumer 5.4.0 → 5.7.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
@@ -9,8 +9,8 @@ import { SQSError, TimeoutError } from './errors';
9
9
  const debug = Debug('sqs-consumer');
10
10
 
11
11
  type ReceieveMessageResponse = PromiseResult<SQS.Types.ReceiveMessageResult, AWSError>;
12
- type SQSMessage = SQS.Types.Message;
13
12
  type ReceiveMessageRequest = SQS.Types.ReceiveMessageRequest;
13
+ export type SQSMessage = SQS.Types.Message;
14
14
 
15
15
  const requiredOptions = [
16
16
  'queueUrl',
@@ -44,9 +44,13 @@ function assertOptions(options: ConsumerOptions): void {
44
44
  if (options.batchSize > 10 || options.batchSize < 1) {
45
45
  throw new Error('SQS batchSize option must be between 1 and 10.');
46
46
  }
47
+
48
+ if (options.heartbeatInterval && !(options.heartbeatInterval < options.visibilityTimeout)) {
49
+ throw new Error('heartbeatInterval must be less than visibilityTimeout.');
50
+ }
47
51
  }
48
52
 
49
- function isConnectionError(err: Error): Boolean {
53
+ function isConnectionError(err: Error): boolean {
50
54
  if (err instanceof SQSError) {
51
55
  return (err.statusCode === 403 || err.code === 'CredentialsError' || err.code === 'UnknownEndpoint');
52
56
  }
@@ -80,13 +84,26 @@ export interface ConsumerOptions {
80
84
  authenticationErrorTimeout?: number;
81
85
  pollingWaitTimeMs?: number;
82
86
  terminateVisibilityTimeout?: boolean;
87
+ heartbeatInterval?: number;
83
88
  sqs?: SQS;
84
89
  region?: string;
85
90
  handleMessageTimeout?: number;
91
+ shouldDeleteMessages?: boolean;
86
92
  handleMessage?(message: SQSMessage): Promise<void>;
87
93
  handleMessageBatch?(messages: SQSMessage[]): Promise<void>;
88
94
  }
89
95
 
96
+ interface Events {
97
+ 'response_processed': [];
98
+ 'empty': [];
99
+ 'message_received': [SQSMessage];
100
+ 'message_processed': [SQSMessage];
101
+ 'error': [Error, void | SQSMessage | SQSMessage[]];
102
+ 'timeout_error': [Error, SQSMessage];
103
+ 'processing_error': [Error, SQSMessage];
104
+ 'stopped': [];
105
+ }
106
+
90
107
  export class Consumer extends EventEmitter {
91
108
  private queueUrl: string;
92
109
  private handleMessage: (message: SQSMessage) => Promise<void>;
@@ -101,7 +118,9 @@ export class Consumer extends EventEmitter {
101
118
  private authenticationErrorTimeout: number;
102
119
  private pollingWaitTimeMs: number;
103
120
  private terminateVisibilityTimeout: boolean;
121
+ private heartbeatInterval: number;
104
122
  private sqs: SQS;
123
+ private shouldDeleteMessages: boolean;
105
124
 
106
125
  constructor(options: ConsumerOptions) {
107
126
  super();
@@ -116,9 +135,11 @@ export class Consumer extends EventEmitter {
116
135
  this.batchSize = options.batchSize || 1;
117
136
  this.visibilityTimeout = options.visibilityTimeout;
118
137
  this.terminateVisibilityTimeout = options.terminateVisibilityTimeout || false;
119
- this.waitTimeSeconds = options.waitTimeSeconds || 20;
120
- this.authenticationErrorTimeout = options.authenticationErrorTimeout || 10000;
121
- this.pollingWaitTimeMs = options.pollingWaitTimeMs || 0;
138
+ this.heartbeatInterval = options.heartbeatInterval;
139
+ this.waitTimeSeconds = options.waitTimeSeconds ?? 20;
140
+ this.authenticationErrorTimeout = options.authenticationErrorTimeout ?? 10000;
141
+ this.pollingWaitTimeMs = options.pollingWaitTimeMs ?? 0;
142
+ this.shouldDeleteMessages = options.shouldDeleteMessages ?? true;
122
143
 
123
144
  this.sqs = options.sqs || new SQS({
124
145
  region: options.region || process.env.AWS_REGION || 'eu-west-1'
@@ -127,6 +148,18 @@ export class Consumer extends EventEmitter {
127
148
  autoBind(this);
128
149
  }
129
150
 
151
+ emit<T extends keyof Events>(event: T, ...args: Events[T]) {
152
+ return super.emit(event, ...args);
153
+ }
154
+
155
+ on<T extends keyof Events>(event: T, listener: (...args: Events[T]) => void): this {
156
+ return super.on(event, listener);
157
+ }
158
+
159
+ once<T extends keyof Events>(event: T, listener: (...args: Events[T]) => void): this {
160
+ return super.once(event, listener);
161
+ }
162
+
130
163
  public get isRunning(): boolean {
131
164
  return !this.stopped;
132
165
  }
@@ -170,7 +203,13 @@ export class Consumer extends EventEmitter {
170
203
  private async processMessage(message: SQSMessage): Promise<void> {
171
204
  this.emit('message_received', message);
172
205
 
206
+ let heartbeat;
173
207
  try {
208
+ if (this.heartbeatInterval) {
209
+ heartbeat = this.startHeartbeat(async () => {
210
+ return this.changeVisibilityTimeout(message, this.visibilityTimeout);
211
+ });
212
+ }
174
213
  await this.executeHandler(message);
175
214
  await this.deleteMessage(message);
176
215
  this.emit('message_processed', message);
@@ -178,12 +217,10 @@ export class Consumer extends EventEmitter {
178
217
  this.emitError(err, message);
179
218
 
180
219
  if (this.terminateVisibilityTimeout) {
181
- try {
182
- await this.terminateVisabilityTimeout(message);
183
- } catch (err) {
184
- this.emit('error', err, message);
185
- }
220
+ await this.changeVisibilityTimeout(message, 0);
186
221
  }
222
+ } finally {
223
+ clearInterval(heartbeat);
187
224
  }
188
225
  }
189
226
 
@@ -198,6 +235,10 @@ export class Consumer extends EventEmitter {
198
235
  }
199
236
 
200
237
  private async deleteMessage(message: SQSMessage): Promise<void> {
238
+ if (!this.shouldDeleteMessages) {
239
+ debug('Skipping message delete since shouldDeleteMessages is set to false');
240
+ return;
241
+ }
201
242
  debug('Deleting message %s', message.MessageId);
202
243
 
203
244
  const deleteParams = {
@@ -230,7 +271,7 @@ export class Consumer extends EventEmitter {
230
271
  } catch (err) {
231
272
  if (err instanceof TimeoutError) {
232
273
  err.message = `Message handler timed out after ${this.handleMessageTimeout}ms: Operation timed out.`;
233
- } else {
274
+ } else if (err instanceof Error) {
234
275
  err.message = `Unexpected message handler failure: ${err.message}`;
235
276
  }
236
277
  throw err;
@@ -239,14 +280,18 @@ export class Consumer extends EventEmitter {
239
280
  }
240
281
  }
241
282
 
242
- private async terminateVisabilityTimeout(message: SQSMessage): Promise<PromiseResult<any, AWSError>> {
243
- return this.sqs
244
- .changeMessageVisibility({
245
- QueueUrl: this.queueUrl,
246
- ReceiptHandle: message.ReceiptHandle,
247
- VisibilityTimeout: 0
248
- })
249
- .promise();
283
+ private async changeVisibilityTimeout(message: SQSMessage, timeout: number): Promise<PromiseResult<any, AWSError>> {
284
+ try {
285
+ return await this.sqs
286
+ .changeMessageVisibility({
287
+ QueueUrl: this.queueUrl,
288
+ ReceiptHandle: message.ReceiptHandle,
289
+ VisibilityTimeout: timeout
290
+ })
291
+ .promise();
292
+ } catch (err) {
293
+ this.emit('error', toSQSError(err, `Error changing visibility timeout: ${err.message}`), message);
294
+ }
250
295
  }
251
296
 
252
297
  private emitError(err: Error, message: SQSMessage): void {
@@ -286,7 +331,7 @@ export class Consumer extends EventEmitter {
286
331
  }
287
332
  return;
288
333
  }).then(() => {
289
- setTimeout(this.poll, currentPollingTimeout);
334
+ setTimeout(this.poll, currentPollingTimeout);
290
335
  }).catch((err) => {
291
336
  this.emit('error', err);
292
337
  });
@@ -297,7 +342,13 @@ export class Consumer extends EventEmitter {
297
342
  this.emit('message_received', message);
298
343
  });
299
344
 
345
+ let heartbeat;
300
346
  try {
347
+ if (this.heartbeatInterval) {
348
+ heartbeat = this.startHeartbeat(async () => {
349
+ return this.changeVisabilityTimeoutBatch(messages, this.visibilityTimeout);
350
+ });
351
+ }
301
352
  await this.executeBatchHandler(messages);
302
353
  await this.deleteMessageBatch(messages);
303
354
  messages.forEach((message) => {
@@ -307,21 +358,23 @@ export class Consumer extends EventEmitter {
307
358
  this.emit('error', err, messages);
308
359
 
309
360
  if (this.terminateVisibilityTimeout) {
310
- try {
311
- await this.terminateVisabilityTimeoutBatch(messages);
312
- } catch (err) {
313
- this.emit('error', err, messages);
314
- }
361
+ await this.changeVisabilityTimeoutBatch(messages, 0);
315
362
  }
363
+ } finally {
364
+ clearInterval(heartbeat);
316
365
  }
317
366
  }
318
367
 
319
368
  private async deleteMessageBatch(messages: SQSMessage[]): Promise<void> {
369
+ if (!this.shouldDeleteMessages) {
370
+ debug('Skipping message delete since shouldDeleteMessages is set to false');
371
+ return;
372
+ }
320
373
  debug('Deleting messages %s', messages.map((msg) => msg.MessageId).join(' ,'));
321
374
 
322
375
  const deleteParams = {
323
376
  QueueUrl: this.queueUrl,
324
- Entries: messages.map(message => ({
377
+ Entries: messages.map((message) => ({
325
378
  Id: message.MessageId,
326
379
  ReceiptHandle: message.ReceiptHandle
327
380
  }))
@@ -345,18 +398,27 @@ export class Consumer extends EventEmitter {
345
398
  }
346
399
  }
347
400
 
348
- private async terminateVisabilityTimeoutBatch(messages: SQSMessage[]): Promise<PromiseResult<any, AWSError>> {
401
+ private async changeVisabilityTimeoutBatch(messages: SQSMessage[], timeout: number): Promise<PromiseResult<any, AWSError>> {
349
402
  const params = {
350
403
  QueueUrl: this.queueUrl,
351
404
  Entries: messages.map((message) => ({
352
405
  Id: message.MessageId,
353
406
  ReceiptHandle: message.ReceiptHandle,
354
- VisibilityTimeout: 0
407
+ VisibilityTimeout: timeout
355
408
  }))
356
409
  };
357
- return this.sqs
358
- .changeMessageVisibilityBatch(params)
359
- .promise();
410
+ try {
411
+ return await this.sqs
412
+ .changeMessageVisibilityBatch(params)
413
+ .promise();
414
+ } catch (err) {
415
+ this.emit('error', toSQSError(err, `Error changing visibility timeout: ${err.message}`), messages);
416
+ }
360
417
  }
361
418
 
419
+ private startHeartbeat(heartbeatFn: () => void): NodeJS.Timeout {
420
+ return setInterval(() => {
421
+ heartbeatFn();
422
+ }, this.heartbeatInterval * 1000);
423
+ }
362
424
  }
package/src/errors.ts CHANGED
@@ -13,7 +13,7 @@ class SQSError extends Error {
13
13
  }
14
14
 
15
15
  class TimeoutError extends Error {
16
- constructor(message: string = 'Operation timed out.') {
16
+ constructor(message = 'Operation timed out.') {
17
17
  super(message);
18
18
  this.message = message;
19
19
  this.name = 'TimeoutError';
package/src/index.ts CHANGED
@@ -1 +1 @@
1
- export { Consumer, ConsumerOptions } from './consumer';
1
+ export { SQSMessage, Consumer, ConsumerOptions } from './consumer';