@fgiova/fastify-sqs-consumer 3.1.1 → 3.2.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 CHANGED
@@ -107,9 +107,38 @@ app.register(sqsConsumer, [
107
107
  ]);
108
108
  ```
109
109
 
110
- ## Decorator
110
+ ## Decorators
111
111
 
112
- The plugin decorates the Fastify instance with `sqsConsumers`, a `Record<string, { consumer: SQSConsumer, meta: { pendingMessages: number } }>` that provides access to the registered consumers and their metadata.
112
+ ### sqsConsumers
113
+
114
+ `sqsConsumers` is a `Record<string, { consumer: SQSConsumer, meta: { pendingMessages: number }, ownedClient?: MiniSQSClient }>` that provides access to the registered consumers and their metadata, keyed by consumer name.
115
+ It is a null-prototype object (`Object.create(null)`): use `Object.hasOwn(app.sqsConsumers, name)` or `name in app.sqsConsumers` instead of `app.sqsConsumers.hasOwnProperty(name)`.
116
+
117
+ The consumer lifecycle (start/stop) is managed by the plugin: do not call `start()` or `stop()` on these instances directly.
118
+
119
+ ### addSQSConsumer
120
+
121
+ `addSQSConsumer(options: ConsumerOptions): string | null` adds a consumer at runtime. `options` accepts the same object used for each entry of the plugin options array (see [Options](#options)); the `ConsumerOptions` type is exported by the package.
122
+
123
+ - Returns the consumer name (the generated uuid when `name` is not provided), or `null` if the consumer was not created.
124
+ - Before the server is ready, the consumer is started together with the others in the `onReady` hook; after that, it is started immediately.
125
+ - Returns `null` (and logs a warning) when a consumer with the same name already exists or when the server is closing.
126
+
127
+ ```js
128
+ import fastify from "fastify";
129
+ import sqsConsumer from "@fgiova/fastify-sqs-consumer";
130
+
131
+ const app = fastify();
132
+ await app.register(sqsConsumer, []);
133
+ await app.listen({ port: 3000 });
134
+
135
+ const name = app.addSQSConsumer({
136
+ arn: "arn:aws:sqs:eu-central-1:000000000000:MyOtherQueue",
137
+ handlerFunction: async (message, fastify) => {
138
+ return true;
139
+ }
140
+ });
141
+ ```
113
142
 
114
143
  ## handlerFunction
115
144
 
@@ -9,12 +9,7 @@ type ConsumerEntry = {
9
9
  };
10
10
  ownedClient?: MiniSQSClient;
11
11
  };
12
- declare module "fastify" {
13
- interface FastifyInstance {
14
- sqsConsumers: Record<string, ConsumerEntry>;
15
- }
16
- }
17
- declare function sqsConsumerPlugin(fastify: FastifyInstance, options: {
12
+ export type ConsumerOptions = {
18
13
  arn: string;
19
14
  credentials?: {
20
15
  accessKeyId: string;
@@ -37,6 +32,13 @@ declare function sqsConsumerPlugin(fastify: FastifyInstance, options: {
37
32
  secretAccessKey: string;
38
33
  };
39
34
  };
40
- }[], done: (err?: Error) => void): void;
35
+ };
36
+ declare module "fastify" {
37
+ interface FastifyInstance {
38
+ sqsConsumers: Record<string, ConsumerEntry>;
39
+ addSQSConsumer: (consumerOptions: ConsumerOptions) => string | null;
40
+ }
41
+ }
42
+ declare function sqsConsumerPlugin(fastify: FastifyInstance, options: ConsumerOptions[], done: (err?: Error) => void): void;
41
43
  export declare const fastifyPlugin: typeof sqsConsumerPlugin;
42
44
  export default fastifyPlugin;
package/dist/cjs/index.js CHANGED
@@ -67,31 +67,60 @@ function createConsumer(fastify, queueArn, handlerFunction, timeout = 90000, wai
67
67
  };
68
68
  }
69
69
  function sqsConsumerPlugin(fastify, options, done) {
70
- const consumers = {};
70
+ const consumers = Object.create(null);
71
71
  let maxExecutionTimeout = 90000;
72
+ let isClosing = false;
73
+ let isReady = false;
72
74
  fastify.decorate("sqsConsumers", consumers);
73
- for (const handler of options) {
74
- maxExecutionTimeout = Math.max(maxExecutionTimeout, handler.timeout ?? 0);
75
- const { name, arn: queueArn, handlerFunction, timeout, waitTimeSeconds, batchSize, attributeNames, messageAttributeNames, events, sqs, parallelExecution, credentials, } = handler;
75
+ const startConsumer = (consumerName) => {
76
+ const { consumer, ownedClient } = consumers[consumerName];
77
+ const consumerStart = consumer.start();
78
+ consumerStart.catch(async (e) => {
79
+ fastify.log.error(e);
80
+ delete consumers[consumerName];
81
+ await consumer.stop().catch((stopErr) => fastify.log.error(stopErr));
82
+ await ownedClient?.destroy().catch((err) => fastify.log.error(err));
83
+ });
84
+ return consumerStart;
85
+ };
86
+ const addSQSConsumer = (consumerOptions) => {
87
+ const { name, arn: queueArn, handlerFunction, timeout, waitTimeSeconds, batchSize, attributeNames, messageAttributeNames, events, sqs, parallelExecution, credentials, } = consumerOptions;
88
+ const consumerName = name || (0, node_crypto_1.randomUUID)();
89
+ if (consumers[consumerName]) {
90
+ fastify.log.warn(`[fastify-sqs-consumer] Consumer with name ${consumerName} already exists. Skipping creation.`);
91
+ return null;
92
+ }
93
+ if (isClosing) {
94
+ fastify.log.warn(`[fastify-sqs-consumer] Cannot add consumer ${consumerName} while closing. Skipping creation.`);
95
+ return null;
96
+ }
97
+ maxExecutionTimeout = Math.max(maxExecutionTimeout, consumerOptions.timeout ?? 0);
76
98
  try {
77
- consumers[name || (0, node_crypto_1.randomUUID)()] = createConsumer(fastify, queueArn, handlerFunction, timeout, waitTimeSeconds, batchSize, attributeNames, messageAttributeNames, parallelExecution, events, sqs, credentials);
78
- } /* c8 ignore next 3 */
99
+ consumers[consumerName] = createConsumer(fastify, queueArn, handlerFunction, timeout, waitTimeSeconds, batchSize, attributeNames, messageAttributeNames, parallelExecution, events, sqs, credentials);
100
+ if (isReady) {
101
+ void startConsumer(consumerName);
102
+ }
103
+ return consumerName;
104
+ } /* c8 ignore next 4 */
79
105
  catch (e) {
80
106
  fastify.log.error(e);
107
+ return null;
81
108
  }
109
+ };
110
+ fastify.decorate("addSQSConsumer", addSQSConsumer);
111
+ for (const handler of options) {
112
+ addSQSConsumer(handler);
82
113
  }
83
114
  fastify.addHook("onReady", (done) => {
115
+ isReady = true;
84
116
  for (const consumerName of Object.keys(consumers)) {
85
- consumers[consumerName].consumer.start().catch(async (e) => {
86
- fastify.log.error(e);
87
- const { consumer, ownedClient } = consumers[consumerName];
88
- delete consumers[consumerName];
89
- await consumer.stop().catch((stopErr) => fastify.log.error(stopErr));
90
- await ownedClient?.destroy().catch((err) => fastify.log.error(err));
91
- });
117
+ void startConsumer(consumerName);
92
118
  }
93
119
  done();
94
120
  });
121
+ fastify.addHook("preClose", async () => {
122
+ isClosing = true;
123
+ });
95
124
  fastify.addHook("onClose", async () => {
96
125
  const arrayConsumers = Object.values(consumers);
97
126
  const deadline = Date.now() + maxExecutionTimeout + 2000;
@@ -9,12 +9,7 @@ type ConsumerEntry = {
9
9
  };
10
10
  ownedClient?: MiniSQSClient;
11
11
  };
12
- declare module "fastify" {
13
- interface FastifyInstance {
14
- sqsConsumers: Record<string, ConsumerEntry>;
15
- }
16
- }
17
- declare function sqsConsumerPlugin(fastify: FastifyInstance, options: {
12
+ export type ConsumerOptions = {
18
13
  arn: string;
19
14
  credentials?: {
20
15
  accessKeyId: string;
@@ -37,6 +32,13 @@ declare function sqsConsumerPlugin(fastify: FastifyInstance, options: {
37
32
  secretAccessKey: string;
38
33
  };
39
34
  };
40
- }[], done: (err?: Error) => void): void;
35
+ };
36
+ declare module "fastify" {
37
+ interface FastifyInstance {
38
+ sqsConsumers: Record<string, ConsumerEntry>;
39
+ addSQSConsumer: (consumerOptions: ConsumerOptions) => string | null;
40
+ }
41
+ }
42
+ declare function sqsConsumerPlugin(fastify: FastifyInstance, options: ConsumerOptions[], done: (err?: Error) => void): void;
41
43
  export declare const fastifyPlugin: typeof sqsConsumerPlugin;
42
44
  export default fastifyPlugin;
@@ -61,31 +61,60 @@ function createConsumer(fastify, queueArn, handlerFunction, timeout = 90000, wai
61
61
  };
62
62
  }
63
63
  function sqsConsumerPlugin(fastify, options, done) {
64
- const consumers = {};
64
+ const consumers = Object.create(null);
65
65
  let maxExecutionTimeout = 90000;
66
+ let isClosing = false;
67
+ let isReady = false;
66
68
  fastify.decorate("sqsConsumers", consumers);
67
- for (const handler of options) {
68
- maxExecutionTimeout = Math.max(maxExecutionTimeout, handler.timeout ?? 0);
69
- const { name, arn: queueArn, handlerFunction, timeout, waitTimeSeconds, batchSize, attributeNames, messageAttributeNames, events, sqs, parallelExecution, credentials, } = handler;
69
+ const startConsumer = (consumerName) => {
70
+ const { consumer, ownedClient } = consumers[consumerName];
71
+ const consumerStart = consumer.start();
72
+ consumerStart.catch(async (e) => {
73
+ fastify.log.error(e);
74
+ delete consumers[consumerName];
75
+ await consumer.stop().catch((stopErr) => fastify.log.error(stopErr));
76
+ await ownedClient?.destroy().catch((err) => fastify.log.error(err));
77
+ });
78
+ return consumerStart;
79
+ };
80
+ const addSQSConsumer = (consumerOptions) => {
81
+ const { name, arn: queueArn, handlerFunction, timeout, waitTimeSeconds, batchSize, attributeNames, messageAttributeNames, events, sqs, parallelExecution, credentials, } = consumerOptions;
82
+ const consumerName = name || randomUUID();
83
+ if (consumers[consumerName]) {
84
+ fastify.log.warn(`[fastify-sqs-consumer] Consumer with name ${consumerName} already exists. Skipping creation.`);
85
+ return null;
86
+ }
87
+ if (isClosing) {
88
+ fastify.log.warn(`[fastify-sqs-consumer] Cannot add consumer ${consumerName} while closing. Skipping creation.`);
89
+ return null;
90
+ }
91
+ maxExecutionTimeout = Math.max(maxExecutionTimeout, consumerOptions.timeout ?? 0);
70
92
  try {
71
- consumers[name || randomUUID()] = createConsumer(fastify, queueArn, handlerFunction, timeout, waitTimeSeconds, batchSize, attributeNames, messageAttributeNames, parallelExecution, events, sqs, credentials);
72
- } /* c8 ignore next 3 */
93
+ consumers[consumerName] = createConsumer(fastify, queueArn, handlerFunction, timeout, waitTimeSeconds, batchSize, attributeNames, messageAttributeNames, parallelExecution, events, sqs, credentials);
94
+ if (isReady) {
95
+ void startConsumer(consumerName);
96
+ }
97
+ return consumerName;
98
+ } /* c8 ignore next 4 */
73
99
  catch (e) {
74
100
  fastify.log.error(e);
101
+ return null;
75
102
  }
103
+ };
104
+ fastify.decorate("addSQSConsumer", addSQSConsumer);
105
+ for (const handler of options) {
106
+ addSQSConsumer(handler);
76
107
  }
77
108
  fastify.addHook("onReady", (done) => {
109
+ isReady = true;
78
110
  for (const consumerName of Object.keys(consumers)) {
79
- consumers[consumerName].consumer.start().catch(async (e) => {
80
- fastify.log.error(e);
81
- const { consumer, ownedClient } = consumers[consumerName];
82
- delete consumers[consumerName];
83
- await consumer.stop().catch((stopErr) => fastify.log.error(stopErr));
84
- await ownedClient?.destroy().catch((err) => fastify.log.error(err));
85
- });
111
+ void startConsumer(consumerName);
86
112
  }
87
113
  done();
88
114
  });
115
+ fastify.addHook("preClose", async () => {
116
+ isClosing = true;
117
+ });
89
118
  fastify.addHook("onClose", async () => {
90
119
  const arrayConsumers = Object.values(consumers);
91
120
  const deadline = Date.now() + maxExecutionTimeout + 2000;
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.1",
4
+ "version": "3.2.0",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "types": "./dist/cjs/index.d.ts",
7
7
  "exports": {
@@ -77,7 +77,7 @@
77
77
  "clock-mock": "^2.0.4",
78
78
  "conventional-changelog-conventionalcommits": "^9.1.0",
79
79
  "dotenv": "^10.0.0",
80
- "fastify": "^5.8.5",
80
+ "fastify": "^5.12.5",
81
81
  "semantic-release": "^25.0.3",
82
82
  "tap": "^21.1.0",
83
83
  "testcontainers": "^11.5.1",
@@ -85,7 +85,7 @@
85
85
  "typescript": "^5.1.6"
86
86
  },
87
87
  "dependencies": {
88
- "@fgiova/sqs-consumer": "^3.2.0",
88
+ "@fgiova/sqs-consumer": "^3.2.1",
89
89
  "@watchable/unpromise": "^1.0.2",
90
90
  "fastify-plugin": "^5.1.0"
91
91
  }