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/.eslintignore +4 -0
- package/.github/CODEOWNERS +2 -0
- package/.github/CONTRIBUTING.md +39 -0
- package/.github/ISSUE_TEMPLATE/bug-report.md +27 -0
- package/.github/ISSUE_TEMPLATE/feature-request.md +20 -0
- package/.github/ISSUE_TEMPLATE/technical-question.md +17 -0
- package/.github/pull_request_template.md +20 -0
- package/.travis.yml +1 -1
- package/README.md +36 -6
- package/dist/bind.js +2 -1
- package/dist/consumer.d.ts +23 -4
- package/dist/consumer.js +77 -30
- package/dist/errors.js +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +35 -19
- package/src/bind.ts +0 -2
- package/src/consumer.ts +93 -31
- package/src/errors.ts +1 -1
- package/src/index.ts +1 -1
- package/test/consumer.ts +226 -86
- package/tsconfig.json +1 -1
- package/.nyc_output/6b82944a-e1d8-43f1-b7e6-4250bba9eb0c.json +0 -1
- package/.nyc_output/b96929df-d0ad-46a6-83d3-f796728a2d38.json +0 -1
- package/CONTRIBUTING.md +0 -8
- package/issue_template.md +0 -6
- package/test/mocha.opts +0 -5
- package/tslint.json +0 -74
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):
|
|
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.
|
|
120
|
-
this.
|
|
121
|
-
this.
|
|
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
|
-
|
|
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
|
|
243
|
-
|
|
244
|
-
.
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
407
|
+
VisibilityTimeout: timeout
|
|
355
408
|
}))
|
|
356
409
|
};
|
|
357
|
-
|
|
358
|
-
.
|
|
359
|
-
|
|
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
|
|
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';
|