@fgiova/fastify-sqs-consumer 3.1.0 → 3.1.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.
@@ -2,14 +2,16 @@ import { type Message, MiniSQSClient } from "@fgiova/mini-sqs-client";
2
2
  import { type HooksOptions, SQSConsumer } from "@fgiova/sqs-consumer";
3
3
  import type { FastifyInstance } from "fastify";
4
4
  import type { Pool } from "undici";
5
+ type ConsumerEntry = {
6
+ consumer: SQSConsumer;
7
+ meta: {
8
+ pendingMessages: number;
9
+ };
10
+ ownedClient?: MiniSQSClient;
11
+ };
5
12
  declare module "fastify" {
6
13
  interface FastifyInstance {
7
- sqsConsumers: Record<string, {
8
- consumer: SQSConsumer;
9
- meta: {
10
- pendingMessages: number;
11
- };
12
- }>;
14
+ sqsConsumers: Record<string, ConsumerEntry>;
13
15
  }
14
16
  }
15
17
  declare function sqsConsumerPlugin(fastify: FastifyInstance, options: {
package/dist/cjs/index.js CHANGED
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.fastifyPlugin = void 0;
7
7
  const node_crypto_1 = require("node:crypto");
8
+ const promises_1 = require("node:timers/promises");
8
9
  const mini_sqs_client_1 = require("@fgiova/mini-sqs-client");
9
10
  const sqs_consumer_1 = require("@fgiova/sqs-consumer");
10
11
  // @ts-expect-error
@@ -12,11 +13,13 @@ const unpromise_1 = require("@watchable/unpromise");
12
13
  const fastify_plugin_1 = __importDefault(require("fastify-plugin"));
13
14
  function createConsumer(fastify, queueArn, handlerFunction, timeout = 90000, waitTimeSeconds = 20, batchSize = 1, attributeNames = [], messageAttributeNames = [], parallelExecution, hooks, sqs, credentials) {
14
15
  const meta = { pendingMessages: 0 };
15
- credentials =
16
- credentials ??
17
- (typeof sqs === "object" && !(sqs instanceof mini_sqs_client_1.MiniSQSClient)
18
- ? sqs.credentials
19
- : undefined);
16
+ let ownedClient;
17
+ if (!(sqs instanceof mini_sqs_client_1.MiniSQSClient)) {
18
+ credentials = credentials ?? sqs?.credentials;
19
+ ownedClient = new mini_sqs_client_1.MiniSQSClient(queueArn.split(":")[3], sqs?.endpoint, sqs?.undiciOptions,
20
+ /* c8 ignore next 1 */
21
+ credentials ? { credentials } : undefined);
22
+ }
20
23
  return {
21
24
  consumer: new sqs_consumer_1.SQSConsumer({
22
25
  queueARN: queueArn,
@@ -34,15 +37,7 @@ function createConsumer(fastify, queueArn, handlerFunction, timeout = 90000, wai
34
37
  },
35
38
  hooks,
36
39
  clientOptions: {
37
- sqsClient: sqs instanceof mini_sqs_client_1.MiniSQSClient ? sqs : undefined,
38
- endpoint: typeof sqs === "object" && !(sqs instanceof mini_sqs_client_1.MiniSQSClient)
39
- ? sqs.endpoint
40
- : undefined,
41
- undiciOptions: typeof sqs === "object" && !(sqs instanceof mini_sqs_client_1.MiniSQSClient)
42
- ? sqs.undiciOptions
43
- : undefined,
44
- /* c8 ignore next 1 */
45
- signer: credentials ? { credentials } : undefined,
40
+ sqsClient: ownedClient ?? sqs,
46
41
  },
47
42
  handler: async function handleMessageFunction(message) {
48
43
  let timeoutId;
@@ -51,7 +46,7 @@ function createConsumer(fastify, queueArn, handlerFunction, timeout = 90000, wai
51
46
  return await unpromise_1.Unpromise.race([
52
47
  handlerFunction(message, fastify),
53
48
  new Promise((_, reject) => {
54
- timeoutId = setTimeout(() => reject(new Error("Handler execution timed out")), timeout + 1000);
49
+ timeoutId = setTimeout(() => reject(new Error("Handler execution timed out")), timeout + 1000).unref();
55
50
  }),
56
51
  ]);
57
52
  }
@@ -68,6 +63,7 @@ function createConsumer(fastify, queueArn, handlerFunction, timeout = 90000, wai
68
63
  },
69
64
  }),
70
65
  meta,
66
+ ownedClient,
71
67
  };
72
68
  }
73
69
  function sqsConsumerPlugin(fastify, options, done) {
@@ -88,48 +84,32 @@ function sqsConsumerPlugin(fastify, options, done) {
88
84
  for (const consumerName of Object.keys(consumers)) {
89
85
  consumers[consumerName].consumer.start().catch(async (e) => {
90
86
  fastify.log.error(e);
91
- await consumers[consumerName].consumer
92
- .stop()
93
- .catch((stopErr) => fastify.log.error(stopErr));
87
+ const { consumer, ownedClient } = consumers[consumerName];
94
88
  delete consumers[consumerName];
89
+ await consumer.stop().catch((stopErr) => fastify.log.error(stopErr));
90
+ await ownedClient?.destroy().catch((err) => fastify.log.error(err));
95
91
  });
96
92
  }
97
93
  done();
98
94
  });
99
- fastify.addHook("onClose", (fastify, done) => {
100
- let allStopped = false;
95
+ fastify.addHook("onClose", async () => {
101
96
  const arrayConsumers = Object.values(consumers);
102
- const timeStart = Date.now();
103
- for (const consumer of arrayConsumers) {
104
- consumer.consumer
105
- .stop()
106
- .catch((e) => fastify.log.error(`[fastify-sqs-consumer] Error stopping consumer: ${e}`));
107
- }
108
- const interval = setInterval(() => {
109
- const isRunning = arrayConsumers.some((consumer) => consumer.consumer.isRunning);
110
- const pendingMessages = arrayConsumers.reduce((acc, consumer) => acc + consumer.meta.pendingMessages, 0);
111
- if (Date.now() - timeStart > maxExecutionTimeout + 2000) {
97
+ const deadline = Date.now() + maxExecutionTimeout + 2000;
98
+ let stopped = false;
99
+ Promise.allSettled(arrayConsumers.map(({ consumer }) => consumer
100
+ .stop()
101
+ .catch((e) => fastify.log.error(`[fastify-sqs-consumer] Error stopping consumer: ${e}`)))).then(() => {
102
+ stopped = true;
103
+ });
104
+ while (!stopped ||
105
+ arrayConsumers.some(({ meta }) => meta.pendingMessages > 0)) {
106
+ if (Date.now() > deadline) {
112
107
  fastify.log.warn("[fastify-sqs-consumer] Consumers are taking too long to stop... forcing shutdown");
113
- clearInterval(interval);
114
- return done();
115
- }
116
- /* c8 ignore next 5 */
117
- if (isRunning) {
118
- fastify.log.debug("[fastify-sqs-consumer] Some consumers still running... please wait");
119
- return null;
120
- }
121
- else if (!allStopped) {
122
- allStopped = true;
123
- fastify.log.debug("[fastify-sqs-consumer] All consumers are stopped");
108
+ break;
124
109
  }
125
- if (pendingMessages <= 0) {
126
- clearInterval(interval);
127
- return done();
128
- }
129
- else {
130
- fastify.log.debug(`[fastify-sqs-consumer] ${pendingMessages} pending messages`);
131
- }
132
- }, 500);
110
+ await (0, promises_1.setTimeout)(500);
111
+ }
112
+ await Promise.allSettled(arrayConsumers.map(({ ownedClient }) => ownedClient?.destroy()));
133
113
  });
134
114
  done();
135
115
  }
@@ -2,14 +2,16 @@ import { type Message, MiniSQSClient } from "@fgiova/mini-sqs-client";
2
2
  import { type HooksOptions, SQSConsumer } from "@fgiova/sqs-consumer";
3
3
  import type { FastifyInstance } from "fastify";
4
4
  import type { Pool } from "undici";
5
+ type ConsumerEntry = {
6
+ consumer: SQSConsumer;
7
+ meta: {
8
+ pendingMessages: number;
9
+ };
10
+ ownedClient?: MiniSQSClient;
11
+ };
5
12
  declare module "fastify" {
6
13
  interface FastifyInstance {
7
- sqsConsumers: Record<string, {
8
- consumer: SQSConsumer;
9
- meta: {
10
- pendingMessages: number;
11
- };
12
- }>;
14
+ sqsConsumers: Record<string, ConsumerEntry>;
13
15
  }
14
16
  }
15
17
  declare function sqsConsumerPlugin(fastify: FastifyInstance, options: {
@@ -1,4 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
+ import { setTimeout as sleep } from "node:timers/promises";
2
3
  import { MiniSQSClient } from "@fgiova/mini-sqs-client";
3
4
  import { SQSConsumer } from "@fgiova/sqs-consumer";
4
5
  // @ts-expect-error
@@ -6,11 +7,13 @@ import { Unpromise } from "@watchable/unpromise";
6
7
  import fp from "fastify-plugin";
7
8
  function createConsumer(fastify, queueArn, handlerFunction, timeout = 90000, waitTimeSeconds = 20, batchSize = 1, attributeNames = [], messageAttributeNames = [], parallelExecution, hooks, sqs, credentials) {
8
9
  const meta = { pendingMessages: 0 };
9
- credentials =
10
- credentials ??
11
- (typeof sqs === "object" && !(sqs instanceof MiniSQSClient)
12
- ? sqs.credentials
13
- : undefined);
10
+ let ownedClient;
11
+ if (!(sqs instanceof MiniSQSClient)) {
12
+ credentials = credentials ?? sqs?.credentials;
13
+ ownedClient = new MiniSQSClient(queueArn.split(":")[3], sqs?.endpoint, sqs?.undiciOptions,
14
+ /* c8 ignore next 1 */
15
+ credentials ? { credentials } : undefined);
16
+ }
14
17
  return {
15
18
  consumer: new SQSConsumer({
16
19
  queueARN: queueArn,
@@ -28,15 +31,7 @@ function createConsumer(fastify, queueArn, handlerFunction, timeout = 90000, wai
28
31
  },
29
32
  hooks,
30
33
  clientOptions: {
31
- sqsClient: sqs instanceof MiniSQSClient ? sqs : undefined,
32
- endpoint: typeof sqs === "object" && !(sqs instanceof MiniSQSClient)
33
- ? sqs.endpoint
34
- : undefined,
35
- undiciOptions: typeof sqs === "object" && !(sqs instanceof MiniSQSClient)
36
- ? sqs.undiciOptions
37
- : undefined,
38
- /* c8 ignore next 1 */
39
- signer: credentials ? { credentials } : undefined,
34
+ sqsClient: ownedClient ?? sqs,
40
35
  },
41
36
  handler: async function handleMessageFunction(message) {
42
37
  let timeoutId;
@@ -45,7 +40,7 @@ function createConsumer(fastify, queueArn, handlerFunction, timeout = 90000, wai
45
40
  return await Unpromise.race([
46
41
  handlerFunction(message, fastify),
47
42
  new Promise((_, reject) => {
48
- timeoutId = setTimeout(() => reject(new Error("Handler execution timed out")), timeout + 1000);
43
+ timeoutId = setTimeout(() => reject(new Error("Handler execution timed out")), timeout + 1000).unref();
49
44
  }),
50
45
  ]);
51
46
  }
@@ -62,6 +57,7 @@ function createConsumer(fastify, queueArn, handlerFunction, timeout = 90000, wai
62
57
  },
63
58
  }),
64
59
  meta,
60
+ ownedClient,
65
61
  };
66
62
  }
67
63
  function sqsConsumerPlugin(fastify, options, done) {
@@ -82,48 +78,32 @@ function sqsConsumerPlugin(fastify, options, done) {
82
78
  for (const consumerName of Object.keys(consumers)) {
83
79
  consumers[consumerName].consumer.start().catch(async (e) => {
84
80
  fastify.log.error(e);
85
- await consumers[consumerName].consumer
86
- .stop()
87
- .catch((stopErr) => fastify.log.error(stopErr));
81
+ const { consumer, ownedClient } = consumers[consumerName];
88
82
  delete consumers[consumerName];
83
+ await consumer.stop().catch((stopErr) => fastify.log.error(stopErr));
84
+ await ownedClient?.destroy().catch((err) => fastify.log.error(err));
89
85
  });
90
86
  }
91
87
  done();
92
88
  });
93
- fastify.addHook("onClose", (fastify, done) => {
94
- let allStopped = false;
89
+ fastify.addHook("onClose", async () => {
95
90
  const arrayConsumers = Object.values(consumers);
96
- const timeStart = Date.now();
97
- for (const consumer of arrayConsumers) {
98
- consumer.consumer
99
- .stop()
100
- .catch((e) => fastify.log.error(`[fastify-sqs-consumer] Error stopping consumer: ${e}`));
101
- }
102
- const interval = setInterval(() => {
103
- const isRunning = arrayConsumers.some((consumer) => consumer.consumer.isRunning);
104
- const pendingMessages = arrayConsumers.reduce((acc, consumer) => acc + consumer.meta.pendingMessages, 0);
105
- if (Date.now() - timeStart > maxExecutionTimeout + 2000) {
91
+ const deadline = Date.now() + maxExecutionTimeout + 2000;
92
+ let stopped = false;
93
+ Promise.allSettled(arrayConsumers.map(({ consumer }) => consumer
94
+ .stop()
95
+ .catch((e) => fastify.log.error(`[fastify-sqs-consumer] Error stopping consumer: ${e}`)))).then(() => {
96
+ stopped = true;
97
+ });
98
+ while (!stopped ||
99
+ arrayConsumers.some(({ meta }) => meta.pendingMessages > 0)) {
100
+ if (Date.now() > deadline) {
106
101
  fastify.log.warn("[fastify-sqs-consumer] Consumers are taking too long to stop... forcing shutdown");
107
- clearInterval(interval);
108
- return done();
109
- }
110
- /* c8 ignore next 5 */
111
- if (isRunning) {
112
- fastify.log.debug("[fastify-sqs-consumer] Some consumers still running... please wait");
113
- return null;
114
- }
115
- else if (!allStopped) {
116
- allStopped = true;
117
- fastify.log.debug("[fastify-sqs-consumer] All consumers are stopped");
102
+ break;
118
103
  }
119
- if (pendingMessages <= 0) {
120
- clearInterval(interval);
121
- return done();
122
- }
123
- else {
124
- fastify.log.debug(`[fastify-sqs-consumer] ${pendingMessages} pending messages`);
125
- }
126
- }, 500);
104
+ await sleep(500);
105
+ }
106
+ await Promise.allSettled(arrayConsumers.map(({ ownedClient }) => ownedClient?.destroy()));
127
107
  });
128
108
  done();
129
109
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fgiova/fastify-sqs-consumer",
3
3
  "description": "sqs-consumer fastify plugin",
4
- "version": "3.1.0",
4
+ "version": "3.1.1",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "types": "./dist/cjs/index.d.ts",
7
7
  "exports": {
@@ -29,7 +29,7 @@
29
29
  "test:debug": "tap --only --timeout=0",
30
30
  "test:local": "TEST_LOCAL=true tap",
31
31
  "test:local:debug": "TEST_LOCAL=true tap --only --timeout=0",
32
- "test:coverage": "tap --coverage-report=lcovonly --coverage-report=text"
32
+ "test:coverage": "tap --timeout=90 --coverage-report=lcovonly --coverage-report=text"
33
33
  },
34
34
  "repository": {
35
35
  "type": "git",