@fgiova/sqs-consumer 3.2.0 → 3.3.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/README.md +26 -7
- package/dist/cjs/index.d.ts +3 -2
- package/dist/cjs/index.js +62 -47
- package/dist/esm/index.d.mts +3 -2
- package/dist/esm/index.mjs +62 -47
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -78,13 +78,32 @@ const consumer = new SQSConsumer({
|
|
|
78
78
|
| destroySigner | boolean | true | Whether to destroy the signer when the consumer is destroyed. |
|
|
79
79
|
|
|
80
80
|
### ConsumerOptions
|
|
81
|
-
| Option | Type
|
|
82
|
-
|
|
83
|
-
| visibilityTimeout | number
|
|
84
|
-
| waitTimeSeconds | number
|
|
85
|
-
| itemsPerRequest | number
|
|
86
|
-
| messageAttributeNames | string[]
|
|
87
|
-
| attributeNames | string[]
|
|
81
|
+
| Option | Type | Default | Description |
|
|
82
|
+
|-----------------------|-----------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------|
|
|
83
|
+
| visibilityTimeout | number | 30 | The visibility timeout for the messages in seconds |
|
|
84
|
+
| waitTimeSeconds | number | 20 | The wait time for the receiveMessage call in seconds |
|
|
85
|
+
| itemsPerRequest | number | 10 | The maximum number of messages to be received at once |
|
|
86
|
+
| messageAttributeNames | string[] | [] | The message attribute names to be included in the response |
|
|
87
|
+
| attributeNames | string[] | [] | The attribute names to be included in the response |
|
|
88
|
+
| errorDelaySeconds | number | 0 | Seconds a failed message stays invisible before it can be received again |
|
|
89
|
+
| errorDelayBackoff | (attempt: number) => number | | Returns the delay in seconds for a failed message, given its `ApproximateReceiveCount`. Takes precedence over `errorDelaySeconds` |
|
|
90
|
+
|
|
91
|
+
Delays are truncated to an integer and clamped to the SQS range 0–43200 seconds. When `errorDelayBackoff` is set, `ApproximateReceiveCount` is added to `attributeNames` automatically. If `errorDelayBackoff` throws, the error is reported to `onSQSError` and `errorDelaySeconds` is used instead.
|
|
92
|
+
|
|
93
|
+
> **Note:** SQS counts the 12-hour visibility limit from when the message was received, not from when the delay is set. A delay that exceeds the remaining time (roughly `43200 - executionTimeout - visibility extensions`) is silently rejected by SQS and the message is retried after its current visibility timeout. Keep delays well below 12 hours, and keep their sum below the queue's `MessageRetentionPeriod`, or the message expires without reaching the DLQ.
|
|
94
|
+
>
|
|
95
|
+
> Every retry increments `ApproximateReceiveCount`, so with a redrive policy the message moves to the DLQ after `maxReceiveCount` attempts: size the backoff accordingly. On FIFO queues a delayed message blocks its whole message group until the delay expires.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
const consumer = new SQSConsumer({
|
|
99
|
+
queueARN: "arn:aws:sqs:eu-central-1:000000000000:test",
|
|
100
|
+
handler: async (message) => { /* ... */ },
|
|
101
|
+
consumerOptions: {
|
|
102
|
+
// 1st failure: 2s, 2nd: 4s, 3rd: 8s ... capped at 15 minutes
|
|
103
|
+
errorDelayBackoff: (attempt) => Math.min(2 ** attempt, 900),
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
```
|
|
88
107
|
|
|
89
108
|
### Hooks
|
|
90
109
|
| Option | Type | Description |
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ type ConsumerOptions = {
|
|
|
23
23
|
itemsPerRequest?: number;
|
|
24
24
|
messageAttributeNames?: string[];
|
|
25
25
|
attributeNames?: string[];
|
|
26
|
+
errorDelaySeconds?: number;
|
|
27
|
+
errorDelayBackoff?: (attempt: number) => number;
|
|
26
28
|
};
|
|
27
29
|
type ConsumerHookName = Exclude<HookName, "onStart" | "onStop">;
|
|
28
30
|
export type HooksOptions = {
|
|
@@ -46,9 +48,8 @@ export declare class SQSConsumer {
|
|
|
46
48
|
private readonly sqsClient;
|
|
47
49
|
private readonly hooks;
|
|
48
50
|
private readonly messageHandler;
|
|
49
|
-
private messagesOnFly;
|
|
50
51
|
private running;
|
|
51
|
-
private
|
|
52
|
+
private pollLoop;
|
|
52
53
|
private destroyed;
|
|
53
54
|
private readonly logger;
|
|
54
55
|
constructor(options: SQSConsumerOptions);
|
package/dist/cjs/index.js
CHANGED
|
@@ -17,9 +17,8 @@ var context_2 = require("./context");
|
|
|
17
17
|
Object.defineProperty(exports, "getAbortSignal", { enumerable: true, get: function () { return context_2.getAbortSignal; } });
|
|
18
18
|
class SQSConsumer {
|
|
19
19
|
constructor(options) {
|
|
20
|
-
this.messagesOnFly = 0;
|
|
21
20
|
this.running = false;
|
|
22
|
-
this.
|
|
21
|
+
this.pollLoop = null;
|
|
23
22
|
this.destroyed = false;
|
|
24
23
|
if (!options.queueARN)
|
|
25
24
|
throw new Error("queueARN is required");
|
|
@@ -43,7 +42,16 @@ class SQSConsumer {
|
|
|
43
42
|
waitTimeSeconds: options.consumerOptions?.waitTimeSeconds ?? 20,
|
|
44
43
|
itemsPerRequest: options.consumerOptions?.itemsPerRequest ?? 10,
|
|
45
44
|
messageAttributeNames: options.consumerOptions?.messageAttributeNames ?? [],
|
|
46
|
-
attributeNames:
|
|
45
|
+
attributeNames: [
|
|
46
|
+
...new Set([
|
|
47
|
+
...(options.consumerOptions?.attributeNames ?? []),
|
|
48
|
+
...(options.consumerOptions?.errorDelayBackoff
|
|
49
|
+
? ["ApproximateReceiveCount"]
|
|
50
|
+
: []),
|
|
51
|
+
]),
|
|
52
|
+
],
|
|
53
|
+
errorDelaySeconds: options.consumerOptions?.errorDelaySeconds ?? 0,
|
|
54
|
+
errorDelayBackoff: options.consumerOptions?.errorDelayBackoff,
|
|
47
55
|
};
|
|
48
56
|
this.sqsClient =
|
|
49
57
|
options.clientOptions?.sqsClient ??
|
|
@@ -96,7 +104,7 @@ class SQSConsumer {
|
|
|
96
104
|
const err = new errors_1.TimeoutError("Handler execution timed out");
|
|
97
105
|
abortController.abort(err);
|
|
98
106
|
reject(err);
|
|
99
|
-
}, this.handlerOptions.executionTimeout);
|
|
107
|
+
}, this.handlerOptions.executionTimeout).unref();
|
|
100
108
|
}),
|
|
101
109
|
]);
|
|
102
110
|
}
|
|
@@ -120,19 +128,19 @@ class SQSConsumer {
|
|
|
120
128
|
clearTimeout(timeoutId);
|
|
121
129
|
}
|
|
122
130
|
}
|
|
123
|
-
haExtendVisibilityTimeout(
|
|
124
|
-
if (this.handlerOptions.extendVisibilityTimeout
|
|
125
|
-
const processingMessages = messages.map(
|
|
126
|
-
// biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
|
|
127
|
-
(message) => message.ReceiptHandle);
|
|
131
|
+
haExtendVisibilityTimeout(pendingReceipts, visibilityTimeout) {
|
|
132
|
+
if (this.handlerOptions.extendVisibilityTimeout && visibilityTimeout > 0) {
|
|
128
133
|
return setInterval(async () => {
|
|
134
|
+
/* c8 ignore next 1 */
|
|
135
|
+
if (!pendingReceipts.size)
|
|
136
|
+
return;
|
|
129
137
|
try {
|
|
130
|
-
await this.sqsClient.changeMessageVisibilityBatch(this.queueARN,
|
|
138
|
+
await this.sqsClient.changeMessageVisibilityBatch(this.queueARN, [...pendingReceipts], visibilityTimeout);
|
|
131
139
|
} /* c8 ignore next 3 */
|
|
132
140
|
catch (e) {
|
|
133
141
|
await this.hooks.runHook("onSQSError", e);
|
|
134
142
|
}
|
|
135
|
-
}, visibilityTimeout *
|
|
143
|
+
}, visibilityTimeout * 500);
|
|
136
144
|
}
|
|
137
145
|
return null;
|
|
138
146
|
}
|
|
@@ -141,10 +149,24 @@ class SQSConsumer {
|
|
|
141
149
|
const candidateToRelease = [];
|
|
142
150
|
for (const result of messagesResults) {
|
|
143
151
|
if (result.error) {
|
|
144
|
-
|
|
145
|
-
|
|
152
|
+
let delaySeconds = this.consumerOptions.errorDelaySeconds;
|
|
153
|
+
if (this.consumerOptions.errorDelayBackoff) {
|
|
154
|
+
try {
|
|
155
|
+
delaySeconds = this.consumerOptions.errorDelayBackoff(
|
|
156
|
+
/* c8 ignore next 1 */
|
|
157
|
+
Number(result.message.Attributes?.ApproximateReceiveCount ?? 0));
|
|
158
|
+
}
|
|
159
|
+
catch (e) {
|
|
160
|
+
await this.hooks.runHook("onSQSError", e);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
candidateToRelease.push({
|
|
164
|
+
// biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
|
|
165
|
+
receiptHandle: result.message.ReceiptHandle,
|
|
166
|
+
visibilityTimeout: Math.min(43200, Math.max(0, Math.trunc(delaySeconds) || 0)),
|
|
167
|
+
});
|
|
146
168
|
}
|
|
147
|
-
else if (this.handlerOptions.deleteMessage
|
|
169
|
+
else if (this.handlerOptions.deleteMessage) {
|
|
148
170
|
// biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
|
|
149
171
|
candidatesToDelete.push(result.message.ReceiptHandle);
|
|
150
172
|
}
|
|
@@ -153,16 +175,14 @@ class SQSConsumer {
|
|
|
153
175
|
await this.sqsClient.deleteMessageBatch(this.queueARN, candidatesToDelete);
|
|
154
176
|
}
|
|
155
177
|
if (candidateToRelease.length) {
|
|
156
|
-
await this.sqsClient.changeMessageVisibilityBatch(this.queueARN, candidateToRelease
|
|
178
|
+
await this.sqsClient.changeMessageVisibilityBatch(this.queueARN, candidateToRelease);
|
|
157
179
|
}
|
|
158
180
|
}
|
|
159
181
|
async pollMessages() {
|
|
160
182
|
let consecutiveErrors = 0;
|
|
161
183
|
while (this.running) {
|
|
162
184
|
try {
|
|
163
|
-
|
|
164
|
-
/* c8 ignore next 1 */
|
|
165
|
-
const visibilityTimeout = this.consumerOptions.visibilityTimeout ?? 30;
|
|
185
|
+
const visibilityTimeout = this.consumerOptions.visibilityTimeout;
|
|
166
186
|
const messagesResult = await this.sqsClient.receiveMessage(this.queueARN, {
|
|
167
187
|
WaitTimeSeconds: this.consumerOptions.waitTimeSeconds,
|
|
168
188
|
MaxNumberOfMessages: this.consumerOptions.itemsPerRequest,
|
|
@@ -173,34 +193,28 @@ class SQSConsumer {
|
|
|
173
193
|
const messages = await this.hooks.runHook("onPoll",
|
|
174
194
|
/* c8 ignore next 1 */
|
|
175
195
|
messagesResult.Messages ?? []);
|
|
176
|
-
this.messagesOnFly += messages.length;
|
|
177
196
|
if (messages.length) {
|
|
178
|
-
const
|
|
197
|
+
const pendingReceipts = new Set(
|
|
198
|
+
// biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
|
|
199
|
+
messages.map((message) => message.ReceiptHandle));
|
|
200
|
+
const haTimeout = this.haExtendVisibilityTimeout(pendingReceipts, visibilityTimeout);
|
|
179
201
|
try {
|
|
180
|
-
if (this.handlerOptions.parallelExecution
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
finally {
|
|
191
|
-
this.messagesOnFly -= messages.length;
|
|
192
|
-
}
|
|
202
|
+
if (this.handlerOptions.parallelExecution) {
|
|
203
|
+
const results = await (0, p_map_1.default)(messages, async (message) => {
|
|
204
|
+
return await this.runOnMessageHook(message);
|
|
205
|
+
}, {
|
|
206
|
+
concurrency: 10,
|
|
207
|
+
stopOnError: false,
|
|
208
|
+
});
|
|
209
|
+
pendingReceipts.clear();
|
|
210
|
+
await this.deleteMessages(results);
|
|
193
211
|
}
|
|
194
212
|
else {
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
finally {
|
|
202
|
-
this.messagesOnFly -= 1;
|
|
203
|
-
}
|
|
213
|
+
for (const message of messages) {
|
|
214
|
+
const result = await this.runOnMessageHook(message);
|
|
215
|
+
// biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
|
|
216
|
+
pendingReceipts.delete(message.ReceiptHandle);
|
|
217
|
+
await this.deleteMessages([result]);
|
|
204
218
|
}
|
|
205
219
|
}
|
|
206
220
|
}
|
|
@@ -215,17 +229,20 @@ class SQSConsumer {
|
|
|
215
229
|
await this.hooks.runHook("onSQSError", e);
|
|
216
230
|
await (0, promises_1.setTimeout)(Math.min(1000 * 2 ** consecutiveErrors++, 30000));
|
|
217
231
|
}
|
|
218
|
-
this.polling = false;
|
|
219
232
|
}
|
|
220
233
|
}
|
|
221
234
|
async start() {
|
|
222
235
|
try {
|
|
223
236
|
if (this.running)
|
|
224
237
|
throw new Error("Consumer is already running");
|
|
238
|
+
if (this.pollLoop)
|
|
239
|
+
throw new Error("Consumer is stopping");
|
|
225
240
|
if (this.destroyed)
|
|
226
241
|
throw new Error("Consumer is destroyed");
|
|
227
242
|
this.running = true;
|
|
228
|
-
|
|
243
|
+
this.pollLoop = this.pollMessages().finally(() => {
|
|
244
|
+
this.pollLoop = null;
|
|
245
|
+
});
|
|
229
246
|
}
|
|
230
247
|
finally {
|
|
231
248
|
await this.hooks.runHook("onStart", this);
|
|
@@ -236,9 +253,7 @@ class SQSConsumer {
|
|
|
236
253
|
if (!this.running)
|
|
237
254
|
throw new Error("Consumer is not running");
|
|
238
255
|
this.running = false;
|
|
239
|
-
|
|
240
|
-
await (0, promises_1.setTimeout)(500);
|
|
241
|
-
}
|
|
256
|
+
await this.pollLoop;
|
|
242
257
|
if (destroy) {
|
|
243
258
|
await this.sqsClient.destroy(this.clientOptions.destroySigner);
|
|
244
259
|
this.destroyed = true;
|
package/dist/esm/index.d.mts
CHANGED
|
@@ -23,6 +23,8 @@ type ConsumerOptions = {
|
|
|
23
23
|
itemsPerRequest?: number;
|
|
24
24
|
messageAttributeNames?: string[];
|
|
25
25
|
attributeNames?: string[];
|
|
26
|
+
errorDelaySeconds?: number;
|
|
27
|
+
errorDelayBackoff?: (attempt: number) => number;
|
|
26
28
|
};
|
|
27
29
|
type ConsumerHookName = Exclude<HookName, "onStart" | "onStop">;
|
|
28
30
|
export type HooksOptions = {
|
|
@@ -46,9 +48,8 @@ export declare class SQSConsumer {
|
|
|
46
48
|
private readonly sqsClient;
|
|
47
49
|
private readonly hooks;
|
|
48
50
|
private readonly messageHandler;
|
|
49
|
-
private messagesOnFly;
|
|
50
51
|
private running;
|
|
51
|
-
private
|
|
52
|
+
private pollLoop;
|
|
52
53
|
private destroyed;
|
|
53
54
|
private readonly logger;
|
|
54
55
|
constructor(options: SQSConsumerOptions);
|
package/dist/esm/index.mjs
CHANGED
|
@@ -10,9 +10,8 @@ import { Hooks } from "./hooks.mjs";
|
|
|
10
10
|
export { getAbortSignal } from "./context.mjs";
|
|
11
11
|
export class SQSConsumer {
|
|
12
12
|
constructor(options) {
|
|
13
|
-
this.messagesOnFly = 0;
|
|
14
13
|
this.running = false;
|
|
15
|
-
this.
|
|
14
|
+
this.pollLoop = null;
|
|
16
15
|
this.destroyed = false;
|
|
17
16
|
if (!options.queueARN)
|
|
18
17
|
throw new Error("queueARN is required");
|
|
@@ -36,7 +35,16 @@ export class SQSConsumer {
|
|
|
36
35
|
waitTimeSeconds: options.consumerOptions?.waitTimeSeconds ?? 20,
|
|
37
36
|
itemsPerRequest: options.consumerOptions?.itemsPerRequest ?? 10,
|
|
38
37
|
messageAttributeNames: options.consumerOptions?.messageAttributeNames ?? [],
|
|
39
|
-
attributeNames:
|
|
38
|
+
attributeNames: [
|
|
39
|
+
...new Set([
|
|
40
|
+
...(options.consumerOptions?.attributeNames ?? []),
|
|
41
|
+
...(options.consumerOptions?.errorDelayBackoff
|
|
42
|
+
? ["ApproximateReceiveCount"]
|
|
43
|
+
: []),
|
|
44
|
+
]),
|
|
45
|
+
],
|
|
46
|
+
errorDelaySeconds: options.consumerOptions?.errorDelaySeconds ?? 0,
|
|
47
|
+
errorDelayBackoff: options.consumerOptions?.errorDelayBackoff,
|
|
40
48
|
};
|
|
41
49
|
this.sqsClient =
|
|
42
50
|
options.clientOptions?.sqsClient ??
|
|
@@ -89,7 +97,7 @@ export class SQSConsumer {
|
|
|
89
97
|
const err = new TimeoutError("Handler execution timed out");
|
|
90
98
|
abortController.abort(err);
|
|
91
99
|
reject(err);
|
|
92
|
-
}, this.handlerOptions.executionTimeout);
|
|
100
|
+
}, this.handlerOptions.executionTimeout).unref();
|
|
93
101
|
}),
|
|
94
102
|
]);
|
|
95
103
|
}
|
|
@@ -113,19 +121,19 @@ export class SQSConsumer {
|
|
|
113
121
|
clearTimeout(timeoutId);
|
|
114
122
|
}
|
|
115
123
|
}
|
|
116
|
-
haExtendVisibilityTimeout(
|
|
117
|
-
if (this.handlerOptions.extendVisibilityTimeout
|
|
118
|
-
const processingMessages = messages.map(
|
|
119
|
-
// biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
|
|
120
|
-
(message) => message.ReceiptHandle);
|
|
124
|
+
haExtendVisibilityTimeout(pendingReceipts, visibilityTimeout) {
|
|
125
|
+
if (this.handlerOptions.extendVisibilityTimeout && visibilityTimeout > 0) {
|
|
121
126
|
return setInterval(async () => {
|
|
127
|
+
/* c8 ignore next 1 */
|
|
128
|
+
if (!pendingReceipts.size)
|
|
129
|
+
return;
|
|
122
130
|
try {
|
|
123
|
-
await this.sqsClient.changeMessageVisibilityBatch(this.queueARN,
|
|
131
|
+
await this.sqsClient.changeMessageVisibilityBatch(this.queueARN, [...pendingReceipts], visibilityTimeout);
|
|
124
132
|
} /* c8 ignore next 3 */
|
|
125
133
|
catch (e) {
|
|
126
134
|
await this.hooks.runHook("onSQSError", e);
|
|
127
135
|
}
|
|
128
|
-
}, visibilityTimeout *
|
|
136
|
+
}, visibilityTimeout * 500);
|
|
129
137
|
}
|
|
130
138
|
return null;
|
|
131
139
|
}
|
|
@@ -134,10 +142,24 @@ export class SQSConsumer {
|
|
|
134
142
|
const candidateToRelease = [];
|
|
135
143
|
for (const result of messagesResults) {
|
|
136
144
|
if (result.error) {
|
|
137
|
-
|
|
138
|
-
|
|
145
|
+
let delaySeconds = this.consumerOptions.errorDelaySeconds;
|
|
146
|
+
if (this.consumerOptions.errorDelayBackoff) {
|
|
147
|
+
try {
|
|
148
|
+
delaySeconds = this.consumerOptions.errorDelayBackoff(
|
|
149
|
+
/* c8 ignore next 1 */
|
|
150
|
+
Number(result.message.Attributes?.ApproximateReceiveCount ?? 0));
|
|
151
|
+
}
|
|
152
|
+
catch (e) {
|
|
153
|
+
await this.hooks.runHook("onSQSError", e);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
candidateToRelease.push({
|
|
157
|
+
// biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
|
|
158
|
+
receiptHandle: result.message.ReceiptHandle,
|
|
159
|
+
visibilityTimeout: Math.min(43200, Math.max(0, Math.trunc(delaySeconds) || 0)),
|
|
160
|
+
});
|
|
139
161
|
}
|
|
140
|
-
else if (this.handlerOptions.deleteMessage
|
|
162
|
+
else if (this.handlerOptions.deleteMessage) {
|
|
141
163
|
// biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
|
|
142
164
|
candidatesToDelete.push(result.message.ReceiptHandle);
|
|
143
165
|
}
|
|
@@ -146,16 +168,14 @@ export class SQSConsumer {
|
|
|
146
168
|
await this.sqsClient.deleteMessageBatch(this.queueARN, candidatesToDelete);
|
|
147
169
|
}
|
|
148
170
|
if (candidateToRelease.length) {
|
|
149
|
-
await this.sqsClient.changeMessageVisibilityBatch(this.queueARN, candidateToRelease
|
|
171
|
+
await this.sqsClient.changeMessageVisibilityBatch(this.queueARN, candidateToRelease);
|
|
150
172
|
}
|
|
151
173
|
}
|
|
152
174
|
async pollMessages() {
|
|
153
175
|
let consecutiveErrors = 0;
|
|
154
176
|
while (this.running) {
|
|
155
177
|
try {
|
|
156
|
-
|
|
157
|
-
/* c8 ignore next 1 */
|
|
158
|
-
const visibilityTimeout = this.consumerOptions.visibilityTimeout ?? 30;
|
|
178
|
+
const visibilityTimeout = this.consumerOptions.visibilityTimeout;
|
|
159
179
|
const messagesResult = await this.sqsClient.receiveMessage(this.queueARN, {
|
|
160
180
|
WaitTimeSeconds: this.consumerOptions.waitTimeSeconds,
|
|
161
181
|
MaxNumberOfMessages: this.consumerOptions.itemsPerRequest,
|
|
@@ -166,34 +186,28 @@ export class SQSConsumer {
|
|
|
166
186
|
const messages = await this.hooks.runHook("onPoll",
|
|
167
187
|
/* c8 ignore next 1 */
|
|
168
188
|
messagesResult.Messages ?? []);
|
|
169
|
-
this.messagesOnFly += messages.length;
|
|
170
189
|
if (messages.length) {
|
|
171
|
-
const
|
|
190
|
+
const pendingReceipts = new Set(
|
|
191
|
+
// biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
|
|
192
|
+
messages.map((message) => message.ReceiptHandle));
|
|
193
|
+
const haTimeout = this.haExtendVisibilityTimeout(pendingReceipts, visibilityTimeout);
|
|
172
194
|
try {
|
|
173
|
-
if (this.handlerOptions.parallelExecution
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
finally {
|
|
184
|
-
this.messagesOnFly -= messages.length;
|
|
185
|
-
}
|
|
195
|
+
if (this.handlerOptions.parallelExecution) {
|
|
196
|
+
const results = await pMap(messages, async (message) => {
|
|
197
|
+
return await this.runOnMessageHook(message);
|
|
198
|
+
}, {
|
|
199
|
+
concurrency: 10,
|
|
200
|
+
stopOnError: false,
|
|
201
|
+
});
|
|
202
|
+
pendingReceipts.clear();
|
|
203
|
+
await this.deleteMessages(results);
|
|
186
204
|
}
|
|
187
205
|
else {
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
finally {
|
|
195
|
-
this.messagesOnFly -= 1;
|
|
196
|
-
}
|
|
206
|
+
for (const message of messages) {
|
|
207
|
+
const result = await this.runOnMessageHook(message);
|
|
208
|
+
// biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
|
|
209
|
+
pendingReceipts.delete(message.ReceiptHandle);
|
|
210
|
+
await this.deleteMessages([result]);
|
|
197
211
|
}
|
|
198
212
|
}
|
|
199
213
|
}
|
|
@@ -208,17 +222,20 @@ export class SQSConsumer {
|
|
|
208
222
|
await this.hooks.runHook("onSQSError", e);
|
|
209
223
|
await setTimeoutAsync(Math.min(1000 * 2 ** consecutiveErrors++, 30000));
|
|
210
224
|
}
|
|
211
|
-
this.polling = false;
|
|
212
225
|
}
|
|
213
226
|
}
|
|
214
227
|
async start() {
|
|
215
228
|
try {
|
|
216
229
|
if (this.running)
|
|
217
230
|
throw new Error("Consumer is already running");
|
|
231
|
+
if (this.pollLoop)
|
|
232
|
+
throw new Error("Consumer is stopping");
|
|
218
233
|
if (this.destroyed)
|
|
219
234
|
throw new Error("Consumer is destroyed");
|
|
220
235
|
this.running = true;
|
|
221
|
-
|
|
236
|
+
this.pollLoop = this.pollMessages().finally(() => {
|
|
237
|
+
this.pollLoop = null;
|
|
238
|
+
});
|
|
222
239
|
}
|
|
223
240
|
finally {
|
|
224
241
|
await this.hooks.runHook("onStart", this);
|
|
@@ -229,9 +246,7 @@ export class SQSConsumer {
|
|
|
229
246
|
if (!this.running)
|
|
230
247
|
throw new Error("Consumer is not running");
|
|
231
248
|
this.running = false;
|
|
232
|
-
|
|
233
|
-
await setTimeoutAsync(500);
|
|
234
|
-
}
|
|
249
|
+
await this.pollLoop;
|
|
235
250
|
if (destroy) {
|
|
236
251
|
await this.sqsClient.destroy(this.clientOptions.destroySigner);
|
|
237
252
|
this.destroyed = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fgiova/sqs-consumer",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"main": "./dist/cjs/index.js",
|
|
5
5
|
"types": "./dist/cjs/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
]
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@fgiova/mini-sqs-client": "^5.
|
|
59
|
+
"@fgiova/mini-sqs-client": "^5.2.0",
|
|
60
60
|
"@watchable/unpromise": "^1.0.2",
|
|
61
61
|
"p-map": "^7.0.3"
|
|
62
62
|
},
|