@fgiova/fastify-sqs-consumer 3.0.1 → 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.
- package/README.md +30 -1
- package/dist/cjs/index.d.ts +18 -8
- package/dist/cjs/index.js +30 -38
- package/dist/esm/index.d.mts +18 -8
- package/dist/esm/index.mjs +30 -38
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -74,10 +74,39 @@ Options are an array of objects with the following properties (one for each queu
|
|
|
74
74
|
| parallelExecution | boolean | Enable parallel execution of message handlers. |
|
|
75
75
|
| credentials | {accessKeyId, secretAccessKey} | Explicit AWS credentials (alternative to environment configuration). |
|
|
76
76
|
| events | object | Events functions for the consumer (detail in next table). |
|
|
77
|
-
| sqs | MiniSQSClient
|
|
77
|
+
| sqs | MiniSQSClient \| SQSOptions | Either an initialized [@fgiova/mini-sqs-client](https://www.npmjs.com/package/@fgiova/mini-sqs-client) instance (useful for testing sessions) or an options object used to build the client (see below). |
|
|
78
78
|
|
|
79
79
|
\* required
|
|
80
80
|
|
|
81
|
+
### sqs options object
|
|
82
|
+
|
|
83
|
+
Instead of passing a pre-built `MiniSQSClient`, you can pass an options object so the plugin builds the client for you:
|
|
84
|
+
|
|
85
|
+
| Option | Type | Description |
|
|
86
|
+
|---------------|---------------------------------|----------------------------------------------------------------------------------------------|
|
|
87
|
+
| endpoint | string | Custom SQS endpoint (e.g. a local LocalStack/ElasticMQ instance). |
|
|
88
|
+
| undiciOptions | Pool.Options | [undici](https://undici.nodejs.org/#/docs/api/Pool) `Pool` options for the underlying HTTP client. |
|
|
89
|
+
| credentials | {accessKeyId, secretAccessKey} | Explicit AWS credentials. The top-level `credentials` option takes precedence over this one. |
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
app.register(sqsConsumer, [
|
|
93
|
+
{
|
|
94
|
+
arn: "arn:aws:sqs:eu-central-1:000000000000:MyQueue",
|
|
95
|
+
sqs: {
|
|
96
|
+
endpoint: "http://localhost:4566",
|
|
97
|
+
undiciOptions: { connections: 1 },
|
|
98
|
+
credentials: {
|
|
99
|
+
accessKeyId: "AWS_ACCESS_KEY_ID",
|
|
100
|
+
secretAccessKey: "AWS_SECRET_ACCESS_KEY"
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
handlerFunction: async (message, fastify) => {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
]);
|
|
108
|
+
```
|
|
109
|
+
|
|
81
110
|
## Decorator
|
|
82
111
|
|
|
83
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.
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import type
|
|
1
|
+
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
|
+
import type { Pool } from "undici";
|
|
5
|
+
type ConsumerEntry = {
|
|
6
|
+
consumer: SQSConsumer;
|
|
7
|
+
meta: {
|
|
8
|
+
pendingMessages: number;
|
|
9
|
+
};
|
|
10
|
+
ownedClient?: MiniSQSClient;
|
|
11
|
+
};
|
|
4
12
|
declare module "fastify" {
|
|
5
13
|
interface FastifyInstance {
|
|
6
|
-
sqsConsumers: Record<string,
|
|
7
|
-
consumer: SQSConsumer;
|
|
8
|
-
meta: {
|
|
9
|
-
pendingMessages: number;
|
|
10
|
-
};
|
|
11
|
-
}>;
|
|
14
|
+
sqsConsumers: Record<string, ConsumerEntry>;
|
|
12
15
|
}
|
|
13
16
|
}
|
|
14
17
|
declare function sqsConsumerPlugin(fastify: FastifyInstance, options: {
|
|
@@ -26,7 +29,14 @@ declare function sqsConsumerPlugin(fastify: FastifyInstance, options: {
|
|
|
26
29
|
attributeNames?: string[];
|
|
27
30
|
events?: HooksOptions;
|
|
28
31
|
parallelExecution?: boolean;
|
|
29
|
-
sqs?: MiniSQSClient
|
|
32
|
+
sqs?: MiniSQSClient | {
|
|
33
|
+
endpoint?: string;
|
|
34
|
+
undiciOptions?: Pool.Options;
|
|
35
|
+
credentials?: {
|
|
36
|
+
accessKeyId: string;
|
|
37
|
+
secretAccessKey: string;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
30
40
|
}[], done: (err?: Error) => void): void;
|
|
31
41
|
export declare const fastifyPlugin: typeof sqsConsumerPlugin;
|
|
32
42
|
export default fastifyPlugin;
|
package/dist/cjs/index.js
CHANGED
|
@@ -5,12 +5,21 @@ 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");
|
|
9
|
+
const mini_sqs_client_1 = require("@fgiova/mini-sqs-client");
|
|
8
10
|
const sqs_consumer_1 = require("@fgiova/sqs-consumer");
|
|
9
11
|
// @ts-expect-error
|
|
10
12
|
const unpromise_1 = require("@watchable/unpromise");
|
|
11
13
|
const fastify_plugin_1 = __importDefault(require("fastify-plugin"));
|
|
12
14
|
function createConsumer(fastify, queueArn, handlerFunction, timeout = 90000, waitTimeSeconds = 20, batchSize = 1, attributeNames = [], messageAttributeNames = [], parallelExecution, hooks, sqs, credentials) {
|
|
13
15
|
const meta = { pendingMessages: 0 };
|
|
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
|
+
}
|
|
14
23
|
return {
|
|
15
24
|
consumer: new sqs_consumer_1.SQSConsumer({
|
|
16
25
|
queueARN: queueArn,
|
|
@@ -28,9 +37,7 @@ function createConsumer(fastify, queueArn, handlerFunction, timeout = 90000, wai
|
|
|
28
37
|
},
|
|
29
38
|
hooks,
|
|
30
39
|
clientOptions: {
|
|
31
|
-
sqsClient: sqs,
|
|
32
|
-
/* c8 ignore next 1 */
|
|
33
|
-
signer: credentials ? { credentials } : undefined,
|
|
40
|
+
sqsClient: ownedClient ?? sqs,
|
|
34
41
|
},
|
|
35
42
|
handler: async function handleMessageFunction(message) {
|
|
36
43
|
let timeoutId;
|
|
@@ -39,7 +46,7 @@ function createConsumer(fastify, queueArn, handlerFunction, timeout = 90000, wai
|
|
|
39
46
|
return await unpromise_1.Unpromise.race([
|
|
40
47
|
handlerFunction(message, fastify),
|
|
41
48
|
new Promise((_, reject) => {
|
|
42
|
-
timeoutId = setTimeout(() => reject(new Error("Handler execution timed out")), timeout + 1000);
|
|
49
|
+
timeoutId = setTimeout(() => reject(new Error("Handler execution timed out")), timeout + 1000).unref();
|
|
43
50
|
}),
|
|
44
51
|
]);
|
|
45
52
|
}
|
|
@@ -56,6 +63,7 @@ function createConsumer(fastify, queueArn, handlerFunction, timeout = 90000, wai
|
|
|
56
63
|
},
|
|
57
64
|
}),
|
|
58
65
|
meta,
|
|
66
|
+
ownedClient,
|
|
59
67
|
};
|
|
60
68
|
}
|
|
61
69
|
function sqsConsumerPlugin(fastify, options, done) {
|
|
@@ -76,48 +84,32 @@ function sqsConsumerPlugin(fastify, options, done) {
|
|
|
76
84
|
for (const consumerName of Object.keys(consumers)) {
|
|
77
85
|
consumers[consumerName].consumer.start().catch(async (e) => {
|
|
78
86
|
fastify.log.error(e);
|
|
79
|
-
|
|
80
|
-
.stop()
|
|
81
|
-
.catch((stopErr) => fastify.log.error(stopErr));
|
|
87
|
+
const { consumer, ownedClient } = consumers[consumerName];
|
|
82
88
|
delete consumers[consumerName];
|
|
89
|
+
await consumer.stop().catch((stopErr) => fastify.log.error(stopErr));
|
|
90
|
+
await ownedClient?.destroy().catch((err) => fastify.log.error(err));
|
|
83
91
|
});
|
|
84
92
|
}
|
|
85
93
|
done();
|
|
86
94
|
});
|
|
87
|
-
fastify.addHook("onClose", (
|
|
88
|
-
let allStopped = false;
|
|
95
|
+
fastify.addHook("onClose", async () => {
|
|
89
96
|
const arrayConsumers = Object.values(consumers);
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (Date.now()
|
|
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) {
|
|
100
107
|
fastify.log.warn("[fastify-sqs-consumer] Consumers are taking too long to stop... forcing shutdown");
|
|
101
|
-
|
|
102
|
-
return done();
|
|
103
|
-
}
|
|
104
|
-
/* c8 ignore next 5 */
|
|
105
|
-
if (isRunning) {
|
|
106
|
-
fastify.log.debug("[fastify-sqs-consumer] Some consumers still running... please wait");
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
else if (!allStopped) {
|
|
110
|
-
allStopped = true;
|
|
111
|
-
fastify.log.debug("[fastify-sqs-consumer] All consumers are stopped");
|
|
108
|
+
break;
|
|
112
109
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
fastify.log.debug(`[fastify-sqs-consumer] ${pendingMessages} pending messages`);
|
|
119
|
-
}
|
|
120
|
-
}, 500);
|
|
110
|
+
await (0, promises_1.setTimeout)(500);
|
|
111
|
+
}
|
|
112
|
+
await Promise.allSettled(arrayConsumers.map(({ ownedClient }) => ownedClient?.destroy()));
|
|
121
113
|
});
|
|
122
114
|
done();
|
|
123
115
|
}
|
package/dist/esm/index.d.mts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import type
|
|
1
|
+
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
|
+
import type { Pool } from "undici";
|
|
5
|
+
type ConsumerEntry = {
|
|
6
|
+
consumer: SQSConsumer;
|
|
7
|
+
meta: {
|
|
8
|
+
pendingMessages: number;
|
|
9
|
+
};
|
|
10
|
+
ownedClient?: MiniSQSClient;
|
|
11
|
+
};
|
|
4
12
|
declare module "fastify" {
|
|
5
13
|
interface FastifyInstance {
|
|
6
|
-
sqsConsumers: Record<string,
|
|
7
|
-
consumer: SQSConsumer;
|
|
8
|
-
meta: {
|
|
9
|
-
pendingMessages: number;
|
|
10
|
-
};
|
|
11
|
-
}>;
|
|
14
|
+
sqsConsumers: Record<string, ConsumerEntry>;
|
|
12
15
|
}
|
|
13
16
|
}
|
|
14
17
|
declare function sqsConsumerPlugin(fastify: FastifyInstance, options: {
|
|
@@ -26,7 +29,14 @@ declare function sqsConsumerPlugin(fastify: FastifyInstance, options: {
|
|
|
26
29
|
attributeNames?: string[];
|
|
27
30
|
events?: HooksOptions;
|
|
28
31
|
parallelExecution?: boolean;
|
|
29
|
-
sqs?: MiniSQSClient
|
|
32
|
+
sqs?: MiniSQSClient | {
|
|
33
|
+
endpoint?: string;
|
|
34
|
+
undiciOptions?: Pool.Options;
|
|
35
|
+
credentials?: {
|
|
36
|
+
accessKeyId: string;
|
|
37
|
+
secretAccessKey: string;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
30
40
|
}[], done: (err?: Error) => void): void;
|
|
31
41
|
export declare const fastifyPlugin: typeof sqsConsumerPlugin;
|
|
32
42
|
export default fastifyPlugin;
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
3
|
+
import { MiniSQSClient } from "@fgiova/mini-sqs-client";
|
|
2
4
|
import { SQSConsumer } from "@fgiova/sqs-consumer";
|
|
3
5
|
// @ts-expect-error
|
|
4
6
|
import { Unpromise } from "@watchable/unpromise";
|
|
5
7
|
import fp from "fastify-plugin";
|
|
6
8
|
function createConsumer(fastify, queueArn, handlerFunction, timeout = 90000, waitTimeSeconds = 20, batchSize = 1, attributeNames = [], messageAttributeNames = [], parallelExecution, hooks, sqs, credentials) {
|
|
7
9
|
const meta = { pendingMessages: 0 };
|
|
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
|
+
}
|
|
8
17
|
return {
|
|
9
18
|
consumer: new SQSConsumer({
|
|
10
19
|
queueARN: queueArn,
|
|
@@ -22,9 +31,7 @@ function createConsumer(fastify, queueArn, handlerFunction, timeout = 90000, wai
|
|
|
22
31
|
},
|
|
23
32
|
hooks,
|
|
24
33
|
clientOptions: {
|
|
25
|
-
sqsClient: sqs,
|
|
26
|
-
/* c8 ignore next 1 */
|
|
27
|
-
signer: credentials ? { credentials } : undefined,
|
|
34
|
+
sqsClient: ownedClient ?? sqs,
|
|
28
35
|
},
|
|
29
36
|
handler: async function handleMessageFunction(message) {
|
|
30
37
|
let timeoutId;
|
|
@@ -33,7 +40,7 @@ function createConsumer(fastify, queueArn, handlerFunction, timeout = 90000, wai
|
|
|
33
40
|
return await Unpromise.race([
|
|
34
41
|
handlerFunction(message, fastify),
|
|
35
42
|
new Promise((_, reject) => {
|
|
36
|
-
timeoutId = setTimeout(() => reject(new Error("Handler execution timed out")), timeout + 1000);
|
|
43
|
+
timeoutId = setTimeout(() => reject(new Error("Handler execution timed out")), timeout + 1000).unref();
|
|
37
44
|
}),
|
|
38
45
|
]);
|
|
39
46
|
}
|
|
@@ -50,6 +57,7 @@ function createConsumer(fastify, queueArn, handlerFunction, timeout = 90000, wai
|
|
|
50
57
|
},
|
|
51
58
|
}),
|
|
52
59
|
meta,
|
|
60
|
+
ownedClient,
|
|
53
61
|
};
|
|
54
62
|
}
|
|
55
63
|
function sqsConsumerPlugin(fastify, options, done) {
|
|
@@ -70,48 +78,32 @@ function sqsConsumerPlugin(fastify, options, done) {
|
|
|
70
78
|
for (const consumerName of Object.keys(consumers)) {
|
|
71
79
|
consumers[consumerName].consumer.start().catch(async (e) => {
|
|
72
80
|
fastify.log.error(e);
|
|
73
|
-
|
|
74
|
-
.stop()
|
|
75
|
-
.catch((stopErr) => fastify.log.error(stopErr));
|
|
81
|
+
const { consumer, ownedClient } = consumers[consumerName];
|
|
76
82
|
delete consumers[consumerName];
|
|
83
|
+
await consumer.stop().catch((stopErr) => fastify.log.error(stopErr));
|
|
84
|
+
await ownedClient?.destroy().catch((err) => fastify.log.error(err));
|
|
77
85
|
});
|
|
78
86
|
}
|
|
79
87
|
done();
|
|
80
88
|
});
|
|
81
|
-
fastify.addHook("onClose", (
|
|
82
|
-
let allStopped = false;
|
|
89
|
+
fastify.addHook("onClose", async () => {
|
|
83
90
|
const arrayConsumers = Object.values(consumers);
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (Date.now()
|
|
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) {
|
|
94
101
|
fastify.log.warn("[fastify-sqs-consumer] Consumers are taking too long to stop... forcing shutdown");
|
|
95
|
-
|
|
96
|
-
return done();
|
|
97
|
-
}
|
|
98
|
-
/* c8 ignore next 5 */
|
|
99
|
-
if (isRunning) {
|
|
100
|
-
fastify.log.debug("[fastify-sqs-consumer] Some consumers still running... please wait");
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
else if (!allStopped) {
|
|
104
|
-
allStopped = true;
|
|
105
|
-
fastify.log.debug("[fastify-sqs-consumer] All consumers are stopped");
|
|
102
|
+
break;
|
|
106
103
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
fastify.log.debug(`[fastify-sqs-consumer] ${pendingMessages} pending messages`);
|
|
113
|
-
}
|
|
114
|
-
}, 500);
|
|
104
|
+
await sleep(500);
|
|
105
|
+
}
|
|
106
|
+
await Promise.allSettled(arrayConsumers.map(({ ownedClient }) => ownedClient?.destroy()));
|
|
115
107
|
});
|
|
116
108
|
done();
|
|
117
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.
|
|
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",
|