@fgiova/sqs-consumer 3.2.0 → 3.2.1

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.
@@ -46,9 +46,8 @@ export declare class SQSConsumer {
46
46
  private readonly sqsClient;
47
47
  private readonly hooks;
48
48
  private readonly messageHandler;
49
- private messagesOnFly;
50
49
  private running;
51
- private polling;
50
+ private pollLoop;
52
51
  private destroyed;
53
52
  private readonly logger;
54
53
  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.polling = false;
21
+ this.pollLoop = null;
23
22
  this.destroyed = false;
24
23
  if (!options.queueARN)
25
24
  throw new Error("queueARN is required");
@@ -96,7 +95,7 @@ class SQSConsumer {
96
95
  const err = new errors_1.TimeoutError("Handler execution timed out");
97
96
  abortController.abort(err);
98
97
  reject(err);
99
- }, this.handlerOptions.executionTimeout);
98
+ }, this.handlerOptions.executionTimeout).unref();
100
99
  }),
101
100
  ]);
102
101
  }
@@ -120,19 +119,17 @@ class SQSConsumer {
120
119
  clearTimeout(timeoutId);
121
120
  }
122
121
  }
123
- haExtendVisibilityTimeout(messages, visibilityTimeout) {
124
- if (this.handlerOptions.extendVisibilityTimeout !== false) {
125
- const processingMessages = messages.map(
126
- // biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
127
- (message) => message.ReceiptHandle);
122
+ haExtendVisibilityTimeout(pendingReceipts, visibilityTimeout) {
123
+ if (this.handlerOptions.extendVisibilityTimeout !== false &&
124
+ visibilityTimeout > 0) {
128
125
  return setInterval(async () => {
129
126
  try {
130
- await this.sqsClient.changeMessageVisibilityBatch(this.queueARN, processingMessages, visibilityTimeout);
127
+ await this.sqsClient.changeMessageVisibilityBatch(this.queueARN, [...pendingReceipts], visibilityTimeout);
131
128
  } /* c8 ignore next 3 */
132
129
  catch (e) {
133
130
  await this.hooks.runHook("onSQSError", e);
134
131
  }
135
- }, visibilityTimeout * 1000 - 5);
132
+ }, visibilityTimeout * 500);
136
133
  }
137
134
  return null;
138
135
  }
@@ -160,7 +157,6 @@ class SQSConsumer {
160
157
  let consecutiveErrors = 0;
161
158
  while (this.running) {
162
159
  try {
163
- this.polling = true;
164
160
  /* c8 ignore next 1 */
165
161
  const visibilityTimeout = this.consumerOptions.visibilityTimeout ?? 30;
166
162
  const messagesResult = await this.sqsClient.receiveMessage(this.queueARN, {
@@ -173,34 +169,27 @@ class SQSConsumer {
173
169
  const messages = await this.hooks.runHook("onPoll",
174
170
  /* c8 ignore next 1 */
175
171
  messagesResult.Messages ?? []);
176
- this.messagesOnFly += messages.length;
177
172
  if (messages.length) {
178
- const haTimeout = this.haExtendVisibilityTimeout(messages, visibilityTimeout);
173
+ const pendingReceipts = new Set(
174
+ // biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
175
+ messages.map((message) => message.ReceiptHandle));
176
+ const haTimeout = this.haExtendVisibilityTimeout(pendingReceipts, visibilityTimeout);
179
177
  try {
180
178
  if (this.handlerOptions.parallelExecution !== false) {
181
- try {
182
- const results = await (0, p_map_1.default)(messages, async (message) => {
183
- return await this.runOnMessageHook(message);
184
- }, {
185
- concurrency: 10,
186
- stopOnError: false,
187
- });
188
- await this.deleteMessages(results);
189
- }
190
- finally {
191
- this.messagesOnFly -= messages.length;
192
- }
179
+ const results = await (0, p_map_1.default)(messages, async (message) => {
180
+ return await this.runOnMessageHook(message);
181
+ }, {
182
+ concurrency: 10,
183
+ stopOnError: false,
184
+ });
185
+ await this.deleteMessages(results);
193
186
  }
194
187
  else {
195
- const handlingMessages = [...messages];
196
- for (let i = 0; i < handlingMessages.length; i++) {
197
- try {
198
- const result = await this.runOnMessageHook(handlingMessages[i]);
199
- await this.deleteMessages([result]);
200
- }
201
- finally {
202
- this.messagesOnFly -= 1;
203
- }
188
+ for (const message of messages) {
189
+ const result = await this.runOnMessageHook(message);
190
+ // biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
191
+ pendingReceipts.delete(message.ReceiptHandle);
192
+ await this.deleteMessages([result]);
204
193
  }
205
194
  }
206
195
  }
@@ -215,17 +204,20 @@ class SQSConsumer {
215
204
  await this.hooks.runHook("onSQSError", e);
216
205
  await (0, promises_1.setTimeout)(Math.min(1000 * 2 ** consecutiveErrors++, 30000));
217
206
  }
218
- this.polling = false;
219
207
  }
220
208
  }
221
209
  async start() {
222
210
  try {
223
211
  if (this.running)
224
212
  throw new Error("Consumer is already running");
213
+ if (this.pollLoop)
214
+ throw new Error("Consumer is stopping");
225
215
  if (this.destroyed)
226
216
  throw new Error("Consumer is destroyed");
227
217
  this.running = true;
228
- void this.pollMessages();
218
+ this.pollLoop = this.pollMessages().finally(() => {
219
+ this.pollLoop = null;
220
+ });
229
221
  }
230
222
  finally {
231
223
  await this.hooks.runHook("onStart", this);
@@ -236,9 +228,7 @@ class SQSConsumer {
236
228
  if (!this.running)
237
229
  throw new Error("Consumer is not running");
238
230
  this.running = false;
239
- while (this.messagesOnFly > 0 || this.polling) {
240
- await (0, promises_1.setTimeout)(500);
241
- }
231
+ await this.pollLoop;
242
232
  if (destroy) {
243
233
  await this.sqsClient.destroy(this.clientOptions.destroySigner);
244
234
  this.destroyed = true;
@@ -46,9 +46,8 @@ export declare class SQSConsumer {
46
46
  private readonly sqsClient;
47
47
  private readonly hooks;
48
48
  private readonly messageHandler;
49
- private messagesOnFly;
50
49
  private running;
51
- private polling;
50
+ private pollLoop;
52
51
  private destroyed;
53
52
  private readonly logger;
54
53
  constructor(options: SQSConsumerOptions);
@@ -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.polling = false;
14
+ this.pollLoop = null;
16
15
  this.destroyed = false;
17
16
  if (!options.queueARN)
18
17
  throw new Error("queueARN is required");
@@ -89,7 +88,7 @@ export class SQSConsumer {
89
88
  const err = new TimeoutError("Handler execution timed out");
90
89
  abortController.abort(err);
91
90
  reject(err);
92
- }, this.handlerOptions.executionTimeout);
91
+ }, this.handlerOptions.executionTimeout).unref();
93
92
  }),
94
93
  ]);
95
94
  }
@@ -113,19 +112,17 @@ export class SQSConsumer {
113
112
  clearTimeout(timeoutId);
114
113
  }
115
114
  }
116
- haExtendVisibilityTimeout(messages, visibilityTimeout) {
117
- if (this.handlerOptions.extendVisibilityTimeout !== false) {
118
- const processingMessages = messages.map(
119
- // biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
120
- (message) => message.ReceiptHandle);
115
+ haExtendVisibilityTimeout(pendingReceipts, visibilityTimeout) {
116
+ if (this.handlerOptions.extendVisibilityTimeout !== false &&
117
+ visibilityTimeout > 0) {
121
118
  return setInterval(async () => {
122
119
  try {
123
- await this.sqsClient.changeMessageVisibilityBatch(this.queueARN, processingMessages, visibilityTimeout);
120
+ await this.sqsClient.changeMessageVisibilityBatch(this.queueARN, [...pendingReceipts], visibilityTimeout);
124
121
  } /* c8 ignore next 3 */
125
122
  catch (e) {
126
123
  await this.hooks.runHook("onSQSError", e);
127
124
  }
128
- }, visibilityTimeout * 1000 - 5);
125
+ }, visibilityTimeout * 500);
129
126
  }
130
127
  return null;
131
128
  }
@@ -153,7 +150,6 @@ export class SQSConsumer {
153
150
  let consecutiveErrors = 0;
154
151
  while (this.running) {
155
152
  try {
156
- this.polling = true;
157
153
  /* c8 ignore next 1 */
158
154
  const visibilityTimeout = this.consumerOptions.visibilityTimeout ?? 30;
159
155
  const messagesResult = await this.sqsClient.receiveMessage(this.queueARN, {
@@ -166,34 +162,27 @@ export class SQSConsumer {
166
162
  const messages = await this.hooks.runHook("onPoll",
167
163
  /* c8 ignore next 1 */
168
164
  messagesResult.Messages ?? []);
169
- this.messagesOnFly += messages.length;
170
165
  if (messages.length) {
171
- const haTimeout = this.haExtendVisibilityTimeout(messages, visibilityTimeout);
166
+ const pendingReceipts = new Set(
167
+ // biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
168
+ messages.map((message) => message.ReceiptHandle));
169
+ const haTimeout = this.haExtendVisibilityTimeout(pendingReceipts, visibilityTimeout);
172
170
  try {
173
171
  if (this.handlerOptions.parallelExecution !== false) {
174
- try {
175
- const results = await pMap(messages, async (message) => {
176
- return await this.runOnMessageHook(message);
177
- }, {
178
- concurrency: 10,
179
- stopOnError: false,
180
- });
181
- await this.deleteMessages(results);
182
- }
183
- finally {
184
- this.messagesOnFly -= messages.length;
185
- }
172
+ const results = await pMap(messages, async (message) => {
173
+ return await this.runOnMessageHook(message);
174
+ }, {
175
+ concurrency: 10,
176
+ stopOnError: false,
177
+ });
178
+ await this.deleteMessages(results);
186
179
  }
187
180
  else {
188
- const handlingMessages = [...messages];
189
- for (let i = 0; i < handlingMessages.length; i++) {
190
- try {
191
- const result = await this.runOnMessageHook(handlingMessages[i]);
192
- await this.deleteMessages([result]);
193
- }
194
- finally {
195
- this.messagesOnFly -= 1;
196
- }
181
+ for (const message of messages) {
182
+ const result = await this.runOnMessageHook(message);
183
+ // biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
184
+ pendingReceipts.delete(message.ReceiptHandle);
185
+ await this.deleteMessages([result]);
197
186
  }
198
187
  }
199
188
  }
@@ -208,17 +197,20 @@ export class SQSConsumer {
208
197
  await this.hooks.runHook("onSQSError", e);
209
198
  await setTimeoutAsync(Math.min(1000 * 2 ** consecutiveErrors++, 30000));
210
199
  }
211
- this.polling = false;
212
200
  }
213
201
  }
214
202
  async start() {
215
203
  try {
216
204
  if (this.running)
217
205
  throw new Error("Consumer is already running");
206
+ if (this.pollLoop)
207
+ throw new Error("Consumer is stopping");
218
208
  if (this.destroyed)
219
209
  throw new Error("Consumer is destroyed");
220
210
  this.running = true;
221
- void this.pollMessages();
211
+ this.pollLoop = this.pollMessages().finally(() => {
212
+ this.pollLoop = null;
213
+ });
222
214
  }
223
215
  finally {
224
216
  await this.hooks.runHook("onStart", this);
@@ -229,9 +221,7 @@ export class SQSConsumer {
229
221
  if (!this.running)
230
222
  throw new Error("Consumer is not running");
231
223
  this.running = false;
232
- while (this.messagesOnFly > 0 || this.polling) {
233
- await setTimeoutAsync(500);
234
- }
224
+ await this.pollLoop;
235
225
  if (destroy) {
236
226
  await this.sqsClient.destroy(this.clientOptions.destroySigner);
237
227
  this.destroyed = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fgiova/sqs-consumer",
3
- "version": "3.2.0",
3
+ "version": "3.2.1",
4
4
  "main": "./dist/cjs/index.js",
5
5
  "types": "./dist/cjs/index.d.ts",
6
6
  "exports": {