@fgiova/sqs-consumer 2.0.1 → 2.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 +10 -9
- package/dist/{hooks.d.ts → cjs/hooks.d.ts} +2 -3
- package/dist/{hooks.js → cjs/hooks.js} +60 -52
- package/dist/{index.d.ts → cjs/index.d.ts} +26 -21
- package/dist/{index.js → cjs/index.js} +43 -36
- package/dist/{logger.d.ts → cjs/logger.d.ts} +1 -0
- package/dist/esm/errors.d.mts +3 -0
- package/dist/esm/errors.mjs +7 -0
- package/dist/esm/hooks.d.mts +13 -0
- package/dist/esm/hooks.mjs +119 -0
- package/dist/esm/index.d.mts +81 -0
- package/dist/esm/index.mjs +220 -0
- package/dist/esm/logger.d.mts +12 -0
- package/dist/esm/logger.mjs +1 -0
- package/package.json +27 -13
- /package/dist/{errors.d.ts → cjs/errors.d.ts} +0 -0
- /package/dist/{errors.js → cjs/errors.js} +0 -0
- /package/dist/{logger.js → cjs/logger.js} +0 -0
package/README.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@fgiova/sqs-consumer)
|
|
4
4
|

|
|
5
5
|
[](http://www.typescriptlang.org/)
|
|
6
|
-
[](https://qlty.sh/gh/fgiova/projects/sqs-consumer)
|
|
7
|
+
[](https://qlty.sh/gh/fgiova/projects/sqs-consumer)
|
|
8
8
|
|
|
9
9
|
## Description
|
|
10
10
|
This module allows consuming SQS messages using @fgiova/mini-sqs-client thorough the aws-json protocol with "undici" as http agent .<br />
|
|
@@ -58,12 +58,13 @@ const consumer = new SQSConsumer({
|
|
|
58
58
|
| destroySigner | boolean | false | Whether to destroy the signer when the consumer is destroyed. |
|
|
59
59
|
|
|
60
60
|
### ConsumerOptions
|
|
61
|
-
| Option
|
|
62
|
-
|
|
63
|
-
| visibilityTimeout
|
|
64
|
-
| waitTimeSeconds
|
|
65
|
-
| itemsPerRequest
|
|
66
|
-
| messageAttributeNames
|
|
61
|
+
| Option | Type | Default | Description |
|
|
62
|
+
|-----------------------|----------|---------|------------------------------------------------------------|
|
|
63
|
+
| visibilityTimeout | number | 30 | The visibility timeout for the messages in seconds |
|
|
64
|
+
| waitTimeSeconds | number | 20 | The wait time for the receiveMessage call in seconds |
|
|
65
|
+
| itemsPerRequest | number | 10 | The maximum number of messages to be received at once |
|
|
66
|
+
| messageAttributeNames | string[] | [] | The message attribute names to be included in the response |
|
|
67
|
+
| attributeNames | string[] | [] | The attribute names to be included in the response |
|
|
67
68
|
|
|
68
69
|
### Hooks
|
|
69
70
|
| Option | Type | Description |
|
|
@@ -88,4 +89,4 @@ SQSConsumer.addHook(hookname: string, func: Function): void
|
|
|
88
89
|
```
|
|
89
90
|
|
|
90
91
|
## License
|
|
91
|
-
Licensed under [MIT](./LICENSE).
|
|
92
|
+
Licensed under [MIT](./LICENSE).
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { Logger } from "./logger";
|
|
2
|
-
import { Message } from "@fgiova/mini-sqs-client";
|
|
1
|
+
import type { Logger } from "./logger";
|
|
3
2
|
export type HookName = "onPoll" | "onMessage" | "onHandlerSuccess" | "onHandlerTimeout" | "onHandlerError" | "onSuccess" | "onError" | "onSQSError";
|
|
4
3
|
export declare class Hooks {
|
|
5
4
|
private readonly hookSymbols;
|
|
@@ -7,7 +6,7 @@ export declare class Hooks {
|
|
|
7
6
|
private readonly logger;
|
|
8
7
|
constructor(logger?: Logger);
|
|
9
8
|
addHook(hook: HookName, fn: Function): void;
|
|
10
|
-
runHook(hook: HookName, ...args: any[]): Promise<
|
|
9
|
+
runHook(hook: HookName, ...args: any[]): Promise<any>;
|
|
11
10
|
private runHookWithVoidReturn;
|
|
12
11
|
private runHookWithMessageReturn;
|
|
13
12
|
private runHookWithBooleanReturn;
|
|
@@ -2,52 +2,49 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Hooks = void 0;
|
|
4
4
|
class Hooks {
|
|
5
|
-
hookSymbols = {
|
|
6
|
-
onPoll: {
|
|
7
|
-
S: Symbol("poll"),
|
|
8
|
-
throwable: true,
|
|
9
|
-
type: "message-return"
|
|
10
|
-
},
|
|
11
|
-
onMessage: {
|
|
12
|
-
S: Symbol("message"),
|
|
13
|
-
throwable: true,
|
|
14
|
-
type: "message-return"
|
|
15
|
-
},
|
|
16
|
-
onHandlerSuccess: {
|
|
17
|
-
S: Symbol("handlerSuccess"),
|
|
18
|
-
throwable: false,
|
|
19
|
-
type: "message-return"
|
|
20
|
-
},
|
|
21
|
-
onHandlerTimeout: {
|
|
22
|
-
S: Symbol("handlerTimeout"),
|
|
23
|
-
throwable: false,
|
|
24
|
-
type: "boolean-return"
|
|
25
|
-
},
|
|
26
|
-
onHandlerError: {
|
|
27
|
-
S: Symbol("handlerError"),
|
|
28
|
-
throwable: false,
|
|
29
|
-
type: "boolean-return"
|
|
30
|
-
},
|
|
31
|
-
onSuccess: {
|
|
32
|
-
S: Symbol("success"),
|
|
33
|
-
throwable: false,
|
|
34
|
-
type: "boolean-return"
|
|
35
|
-
},
|
|
36
|
-
onError: {
|
|
37
|
-
S: Symbol("error"),
|
|
38
|
-
throwable: false,
|
|
39
|
-
type: "boolean-return"
|
|
40
|
-
},
|
|
41
|
-
onSQSError: {
|
|
42
|
-
S: Symbol("sqsError"),
|
|
43
|
-
throwable: false,
|
|
44
|
-
type: "void"
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
48
|
-
hooks;
|
|
49
|
-
logger;
|
|
50
5
|
constructor(logger = console) {
|
|
6
|
+
this.hookSymbols = {
|
|
7
|
+
onPoll: {
|
|
8
|
+
S: Symbol("poll"),
|
|
9
|
+
throwable: true,
|
|
10
|
+
type: "message-return",
|
|
11
|
+
},
|
|
12
|
+
onMessage: {
|
|
13
|
+
S: Symbol("message"),
|
|
14
|
+
throwable: true,
|
|
15
|
+
type: "message-return",
|
|
16
|
+
},
|
|
17
|
+
onHandlerSuccess: {
|
|
18
|
+
S: Symbol("handlerSuccess"),
|
|
19
|
+
throwable: false,
|
|
20
|
+
type: "message-return",
|
|
21
|
+
},
|
|
22
|
+
onHandlerTimeout: {
|
|
23
|
+
S: Symbol("handlerTimeout"),
|
|
24
|
+
throwable: false,
|
|
25
|
+
type: "boolean-return",
|
|
26
|
+
},
|
|
27
|
+
onHandlerError: {
|
|
28
|
+
S: Symbol("handlerError"),
|
|
29
|
+
throwable: false,
|
|
30
|
+
type: "boolean-return",
|
|
31
|
+
},
|
|
32
|
+
onSuccess: {
|
|
33
|
+
S: Symbol("success"),
|
|
34
|
+
throwable: false,
|
|
35
|
+
type: "boolean-return",
|
|
36
|
+
},
|
|
37
|
+
onError: {
|
|
38
|
+
S: Symbol("error"),
|
|
39
|
+
throwable: false,
|
|
40
|
+
type: "boolean-return",
|
|
41
|
+
},
|
|
42
|
+
onSQSError: {
|
|
43
|
+
S: Symbol("sqsError"),
|
|
44
|
+
throwable: false,
|
|
45
|
+
type: "void",
|
|
46
|
+
},
|
|
47
|
+
};
|
|
51
48
|
this.logger = logger;
|
|
52
49
|
this.hooks = {
|
|
53
50
|
[this.hookSymbols.onPoll.S]: [],
|
|
@@ -60,13 +57,13 @@ class Hooks {
|
|
|
60
57
|
[this.hookSymbols.onSQSError.S]: [],
|
|
61
58
|
};
|
|
62
59
|
}
|
|
63
|
-
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
64
60
|
addHook(hook, fn) {
|
|
65
61
|
const hookSymbol = this.hookSymbols[hook];
|
|
66
62
|
if (!hookSymbol)
|
|
67
63
|
throw new Error(`Invalid hook ${hook}`);
|
|
68
64
|
this.hooks[hookSymbol.S]?.push(fn);
|
|
69
65
|
}
|
|
66
|
+
// biome-ignore lint/suspicious/noExplicitAny: args can be anything
|
|
70
67
|
async runHook(hook, ...args) {
|
|
71
68
|
const hookSymbol = this.hookSymbols[hook];
|
|
72
69
|
try {
|
|
@@ -75,13 +72,15 @@ class Hooks {
|
|
|
75
72
|
switch (hookSymbol.type) {
|
|
76
73
|
case "boolean-return":
|
|
77
74
|
return await this.runHookWithBooleanReturn(hookSymbol, ...args);
|
|
78
|
-
case "message-return":
|
|
75
|
+
case "message-return": {
|
|
79
76
|
const [message, ...someArgs] = args;
|
|
80
77
|
return await this.runHookWithMessageReturn(hookSymbol, message, ...someArgs);
|
|
78
|
+
}
|
|
81
79
|
case "void":
|
|
82
80
|
return await this.runHookWithVoidReturn(hookSymbol, ...args);
|
|
83
81
|
}
|
|
84
82
|
/* c8 ignore next 1 */
|
|
83
|
+
// biome-ignore lint/suspicious/noExplicitAny: error type is not important
|
|
85
84
|
}
|
|
86
85
|
catch (error) {
|
|
87
86
|
error.message = `Error running hook ${hook}: ${error.message}`;
|
|
@@ -90,22 +89,31 @@ class Hooks {
|
|
|
90
89
|
this.logger.error(error);
|
|
91
90
|
}
|
|
92
91
|
}
|
|
93
|
-
async runHookWithVoidReturn(hookSymbol,
|
|
92
|
+
async runHookWithVoidReturn(hookSymbol,
|
|
93
|
+
// biome-ignore lint/suspicious/noExplicitAny: args can be anything
|
|
94
|
+
...args) {
|
|
94
95
|
for (const fn of this.hooks[hookSymbol.S]) {
|
|
95
96
|
await fn(...args);
|
|
96
97
|
}
|
|
97
98
|
return;
|
|
98
99
|
}
|
|
99
|
-
async runHookWithMessageReturn(hookSymbol, message,
|
|
100
|
+
async runHookWithMessageReturn(hookSymbol, message,
|
|
101
|
+
// biome-ignore lint/suspicious/noExplicitAny: args can be anything
|
|
102
|
+
...args) {
|
|
100
103
|
for (const fn of this.hooks[hookSymbol.S]) {
|
|
101
|
-
|
|
104
|
+
const returnMessage = await fn(message, ...args);
|
|
105
|
+
if (returnMessage) {
|
|
106
|
+
message = returnMessage;
|
|
107
|
+
}
|
|
102
108
|
}
|
|
103
109
|
return message;
|
|
104
110
|
}
|
|
105
|
-
async runHookWithBooleanReturn(hookSymbol,
|
|
111
|
+
async runHookWithBooleanReturn(hookSymbol,
|
|
112
|
+
// biome-ignore lint/suspicious/noExplicitAny: args can be anything
|
|
113
|
+
...args) {
|
|
106
114
|
for (const fn of this.hooks[hookSymbol.S]) {
|
|
107
115
|
const continueLoop = await fn(...args);
|
|
108
|
-
if (
|
|
116
|
+
if (continueLoop === false) {
|
|
109
117
|
return false;
|
|
110
118
|
}
|
|
111
119
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Signer, SignerOptions } from "@fgiova/aws-signature";
|
|
1
|
+
import type { Signer, SignerOptions } from "@fgiova/aws-signature";
|
|
2
|
+
import { type Message, MiniSQSClient } from "@fgiova/mini-sqs-client";
|
|
2
3
|
import type { Pool } from "undici";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { Logger } from "./logger";
|
|
4
|
+
import { type HookName } from "./hooks";
|
|
5
|
+
import type { Logger } from "./logger";
|
|
6
6
|
type HandlerOptions = {
|
|
7
7
|
deleteMessage?: boolean;
|
|
8
8
|
extendVisibilityTimeout?: boolean;
|
|
@@ -21,15 +21,19 @@ type ConsumerOptions = {
|
|
|
21
21
|
waitTimeSeconds?: number;
|
|
22
22
|
itemsPerRequest?: number;
|
|
23
23
|
messageAttributeNames?: string[];
|
|
24
|
+
attributeNames?: string[];
|
|
24
25
|
};
|
|
25
|
-
type
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
type hookReturnMessages = Promise<Message[]> | Message[] | Promise<void> | void | undefined;
|
|
27
|
+
type hookReturnMessage = Promise<Message> | Message | Promise<void> | void | undefined;
|
|
28
|
+
type hookReturnBoolean = Promise<boolean> | boolean | Promise<void> | void | undefined;
|
|
29
|
+
export type HooksOptions = {
|
|
30
|
+
onPoll?: (messages: Message[]) => hookReturnMessages;
|
|
31
|
+
onMessage?: (message: Message) => hookReturnMessage;
|
|
32
|
+
onHandlerSuccess?: (message: Message) => hookReturnMessage;
|
|
33
|
+
onHandlerTimeout?: (message: Message) => hookReturnBoolean;
|
|
34
|
+
onHandlerError?: (message: Message, error: Error) => hookReturnBoolean;
|
|
35
|
+
onSuccess?: (message: Message) => hookReturnBoolean;
|
|
36
|
+
onError?: (hook: HookName, message: Message, error: Error) => hookReturnBoolean;
|
|
33
37
|
onSQSError?: (error: Error, message?: Message) => Promise<void>;
|
|
34
38
|
};
|
|
35
39
|
export type SQSConsumerOptions = {
|
|
@@ -63,14 +67,15 @@ export declare class SQSConsumer {
|
|
|
63
67
|
private pollMessages;
|
|
64
68
|
start(): Promise<void>;
|
|
65
69
|
stop(destroy?: boolean): Promise<void>;
|
|
66
|
-
|
|
67
|
-
addHook(hookName: "
|
|
68
|
-
addHook(hookName: "
|
|
69
|
-
addHook(hookName: "
|
|
70
|
-
addHook(hookName: "
|
|
71
|
-
addHook(hookName: "
|
|
72
|
-
addHook(hookName: "
|
|
73
|
-
addHook(hookName: "
|
|
74
|
-
addHook(hookName:
|
|
70
|
+
get isRunning(): boolean;
|
|
71
|
+
addHook(hookName: "onPoll", value: (messages: Message[]) => Promise<Message[]>): this;
|
|
72
|
+
addHook(hookName: "onMessage", value: (message: Message) => Promise<Message>): this;
|
|
73
|
+
addHook(hookName: "onHandlerSuccess", value: (message: Message) => Promise<Message>): this;
|
|
74
|
+
addHook(hookName: "onHandlerTimeout", value: (message: Message) => Promise<boolean>): this;
|
|
75
|
+
addHook(hookName: "onHandlerError", value: (message: Message, error: Error) => Promise<boolean>): this;
|
|
76
|
+
addHook(hookName: "onSuccess", value: (message: Message) => Promise<boolean>): this;
|
|
77
|
+
addHook(hookName: "onError", value: (hook: HookName, message: Message, error: Error) => Promise<boolean>): this;
|
|
78
|
+
addHook(hookName: "onSQSError", value: (error: Error, message?: Message) => Promise<void>): this;
|
|
79
|
+
addHook(hookName: HookName, value: Function): this;
|
|
75
80
|
}
|
|
76
81
|
export {};
|
|
@@ -4,26 +4,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.SQSConsumer = void 0;
|
|
7
|
+
/** biome-ignore-all lint/complexity/noBannedTypes: Function required as generic */
|
|
8
|
+
const promises_1 = require("node:timers/promises");
|
|
7
9
|
const mini_sqs_client_1 = require("@fgiova/mini-sqs-client");
|
|
8
|
-
|
|
9
|
-
const errors_1 = require("./errors");
|
|
10
|
-
const promises_1 = require("timers/promises");
|
|
11
|
-
const p_map_1 = __importDefault(require("p-map"));
|
|
10
|
+
// @ts-expect-error
|
|
12
11
|
const unpromise_1 = require("@watchable/unpromise");
|
|
12
|
+
// @ts-expect-error
|
|
13
|
+
const p_map_1 = __importDefault(require("p-map"));
|
|
14
|
+
const errors_1 = require("./errors");
|
|
15
|
+
const hooks_1 = require("./hooks");
|
|
13
16
|
class SQSConsumer {
|
|
14
|
-
queueARN;
|
|
15
|
-
handlerOptions;
|
|
16
|
-
clientOptions;
|
|
17
|
-
consumerOptions;
|
|
18
|
-
sqsClient;
|
|
19
|
-
hooks;
|
|
20
|
-
messageHandler;
|
|
21
|
-
messagesOnFly = 0;
|
|
22
|
-
running = false;
|
|
23
|
-
polling = false;
|
|
24
|
-
destroyed = false;
|
|
25
|
-
logger;
|
|
26
17
|
constructor(options) {
|
|
18
|
+
this.messagesOnFly = 0;
|
|
19
|
+
this.running = false;
|
|
20
|
+
this.polling = false;
|
|
21
|
+
this.destroyed = false;
|
|
27
22
|
if (!options.queueARN)
|
|
28
23
|
throw new Error("queueARN is required");
|
|
29
24
|
if (!options.handler || typeof options.handler !== "function")
|
|
@@ -32,20 +27,25 @@ class SQSConsumer {
|
|
|
32
27
|
this.hooks = new hooks_1.Hooks(this.logger);
|
|
33
28
|
this.queueARN = options.queueARN;
|
|
34
29
|
const region = this.queueARN.split(":").reverse()[2];
|
|
35
|
-
this.clientOptions = {
|
|
30
|
+
this.clientOptions = {
|
|
31
|
+
destroySigner: options.clientOptions?.destroySigner ?? true,
|
|
32
|
+
};
|
|
36
33
|
this.handlerOptions = {
|
|
37
34
|
deleteMessage: options.handlerOptions?.deleteMessage ?? true,
|
|
38
35
|
extendVisibilityTimeout: options.handlerOptions?.extendVisibilityTimeout ?? true,
|
|
39
|
-
executionTimeout: options.handlerOptions?.executionTimeout ??
|
|
40
|
-
parallelExecution: options.handlerOptions?.parallelExecution ?? true
|
|
36
|
+
executionTimeout: options.handlerOptions?.executionTimeout ?? 30000,
|
|
37
|
+
parallelExecution: options.handlerOptions?.parallelExecution ?? true,
|
|
41
38
|
};
|
|
42
39
|
this.consumerOptions = {
|
|
43
40
|
visibilityTimeout: options.consumerOptions?.visibilityTimeout ?? 30,
|
|
44
41
|
waitTimeSeconds: options.consumerOptions?.waitTimeSeconds ?? 20,
|
|
45
42
|
itemsPerRequest: options.consumerOptions?.itemsPerRequest ?? 10,
|
|
46
|
-
messageAttributeNames: options.consumerOptions?.messageAttributeNames ?? []
|
|
43
|
+
messageAttributeNames: options.consumerOptions?.messageAttributeNames ?? [],
|
|
44
|
+
attributeNames: options.consumerOptions?.attributeNames ?? [],
|
|
47
45
|
};
|
|
48
|
-
this.sqsClient =
|
|
46
|
+
this.sqsClient =
|
|
47
|
+
options.clientOptions?.sqsClient ??
|
|
48
|
+
new mini_sqs_client_1.MiniSQSClient(region, options.clientOptions?.endpoint, options.clientOptions?.undiciOptions, options.clientOptions?.signer);
|
|
49
49
|
if (options.hooks) {
|
|
50
50
|
for (const [key, value] of Object.entries(options.hooks)) {
|
|
51
51
|
if (typeof value !== "function")
|
|
@@ -65,14 +65,14 @@ class SQSConsumer {
|
|
|
65
65
|
await this.hooks.runHook("onSuccess", message);
|
|
66
66
|
return {
|
|
67
67
|
message,
|
|
68
|
-
result
|
|
68
|
+
result,
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
catch (e) {
|
|
72
72
|
await this.hooks.runHook("onError", "onMessage", message, e);
|
|
73
73
|
return {
|
|
74
74
|
message,
|
|
75
|
-
error: e
|
|
75
|
+
error: e,
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
78
|
}
|
|
@@ -82,20 +82,18 @@ class SQSConsumer {
|
|
|
82
82
|
if (this.handlerOptions.executionTimeout) {
|
|
83
83
|
const response = await unpromise_1.Unpromise.race([
|
|
84
84
|
handler(message),
|
|
85
|
-
new Promise((
|
|
85
|
+
new Promise((_resolve, reject) => {
|
|
86
86
|
setTimeout(() => {
|
|
87
87
|
reject(new errors_1.TimeoutError("Handler execution timed out"));
|
|
88
88
|
}, this.handlerOptions.executionTimeout);
|
|
89
|
-
})
|
|
89
|
+
}),
|
|
90
90
|
]);
|
|
91
91
|
handlerResult = response;
|
|
92
92
|
}
|
|
93
93
|
else {
|
|
94
94
|
handlerResult = await handler(message);
|
|
95
95
|
}
|
|
96
|
-
await
|
|
97
|
-
this.hooks.runHook("onHandlerSuccess", message),
|
|
98
|
-
]);
|
|
96
|
+
await this.hooks.runHook("onHandlerSuccess", message);
|
|
99
97
|
return handlerResult;
|
|
100
98
|
}
|
|
101
99
|
catch (error) {
|
|
@@ -110,16 +108,17 @@ class SQSConsumer {
|
|
|
110
108
|
}
|
|
111
109
|
haExtendVisibilityTimeout(messages, visibilityTimeout) {
|
|
112
110
|
if (this.handlerOptions.extendVisibilityTimeout !== false) {
|
|
113
|
-
const processingMessages = messages.map(
|
|
111
|
+
const processingMessages = messages.map(
|
|
112
|
+
// biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
|
|
113
|
+
(message) => message.ReceiptHandle);
|
|
114
114
|
return setInterval(async () => {
|
|
115
115
|
try {
|
|
116
116
|
await this.sqsClient.changeMessageVisibilityBatch(this.queueARN, processingMessages, visibilityTimeout);
|
|
117
|
-
}
|
|
118
|
-
/* c8 ignore next 3 */
|
|
117
|
+
} /* c8 ignore next 3 */
|
|
119
118
|
catch (e) {
|
|
120
119
|
await this.hooks.runHook("onSQSError", e);
|
|
121
120
|
}
|
|
122
|
-
},
|
|
121
|
+
}, visibilityTimeout * 1000 - 5);
|
|
123
122
|
}
|
|
124
123
|
return null;
|
|
125
124
|
}
|
|
@@ -128,9 +127,11 @@ class SQSConsumer {
|
|
|
128
127
|
const candidateToRelease = [];
|
|
129
128
|
for (const result of messagesResults) {
|
|
130
129
|
if (result.result && this.handlerOptions.deleteMessage !== false) {
|
|
130
|
+
// biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
|
|
131
131
|
candidatesToDelete.push(result.message.ReceiptHandle);
|
|
132
132
|
}
|
|
133
133
|
else if (result.error) {
|
|
134
|
+
// biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
|
|
134
135
|
candidateToRelease.push(result.message.ReceiptHandle);
|
|
135
136
|
}
|
|
136
137
|
if (candidatesToDelete.length)
|
|
@@ -144,12 +145,14 @@ class SQSConsumer {
|
|
|
144
145
|
return;
|
|
145
146
|
try {
|
|
146
147
|
this.polling = true;
|
|
147
|
-
|
|
148
|
+
/* c8 ignore next 1 */
|
|
149
|
+
const visibilityTimeout = this.consumerOptions.visibilityTimeout ?? 30;
|
|
148
150
|
const messagesResult = await this.sqsClient.receiveMessage(this.queueARN, {
|
|
149
151
|
WaitTimeSeconds: this.consumerOptions.waitTimeSeconds,
|
|
150
152
|
MaxNumberOfMessages: this.consumerOptions.itemsPerRequest,
|
|
151
153
|
VisibilityTimeout: visibilityTimeout,
|
|
152
154
|
MessageAttributeNames: this.consumerOptions.messageAttributeNames,
|
|
155
|
+
AttributeNames: this.consumerOptions.attributeNames,
|
|
153
156
|
});
|
|
154
157
|
/* c8 ignore next 1 */
|
|
155
158
|
const messages = messagesResult.Messages ?? [];
|
|
@@ -158,7 +161,7 @@ class SQSConsumer {
|
|
|
158
161
|
await this.hooks.runHook("onPoll", messages);
|
|
159
162
|
if (messages.length) {
|
|
160
163
|
const haTimeout = this.haExtendVisibilityTimeout(messages, visibilityTimeout);
|
|
161
|
-
this.messagesOnFly +=
|
|
164
|
+
this.messagesOnFly += messages.length - totalMessages;
|
|
162
165
|
if (this.handlerOptions.parallelExecution !== false) {
|
|
163
166
|
const results = await (0, p_map_1.default)(messages, async (message) => {
|
|
164
167
|
return await this.runOnMessageHook(message);
|
|
@@ -212,9 +215,13 @@ class SQSConsumer {
|
|
|
212
215
|
this.destroyed = true;
|
|
213
216
|
}
|
|
214
217
|
}
|
|
215
|
-
|
|
218
|
+
/* c8 ignore next 3 */
|
|
219
|
+
get isRunning() {
|
|
220
|
+
return this.running;
|
|
221
|
+
}
|
|
216
222
|
addHook(hookName, value) {
|
|
217
|
-
|
|
223
|
+
this.hooks.addHook(hookName, value);
|
|
224
|
+
return this;
|
|
218
225
|
}
|
|
219
226
|
}
|
|
220
227
|
exports.SQSConsumer = SQSConsumer;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Represents a generic logger that could be a simple console, pino etc.
|
|
3
3
|
*/
|
|
4
|
+
/** biome-ignore-all lint/suspicious/noExplicitAny: leave message logger as any */
|
|
4
5
|
export interface Logger {
|
|
5
6
|
trace(message?: any, ...optionalParams: any[]): void;
|
|
6
7
|
debug(message?: any, ...optionalParams: any[]): void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Logger } from "./logger.mjs";
|
|
2
|
+
export type HookName = "onPoll" | "onMessage" | "onHandlerSuccess" | "onHandlerTimeout" | "onHandlerError" | "onSuccess" | "onError" | "onSQSError";
|
|
3
|
+
export declare class Hooks {
|
|
4
|
+
private readonly hookSymbols;
|
|
5
|
+
private readonly hooks;
|
|
6
|
+
private readonly logger;
|
|
7
|
+
constructor(logger?: Logger);
|
|
8
|
+
addHook(hook: HookName, fn: Function): void;
|
|
9
|
+
runHook(hook: HookName, ...args: any[]): Promise<any>;
|
|
10
|
+
private runHookWithVoidReturn;
|
|
11
|
+
private runHookWithMessageReturn;
|
|
12
|
+
private runHookWithBooleanReturn;
|
|
13
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
export class Hooks {
|
|
2
|
+
constructor(logger = console) {
|
|
3
|
+
this.hookSymbols = {
|
|
4
|
+
onPoll: {
|
|
5
|
+
S: Symbol("poll"),
|
|
6
|
+
throwable: true,
|
|
7
|
+
type: "message-return",
|
|
8
|
+
},
|
|
9
|
+
onMessage: {
|
|
10
|
+
S: Symbol("message"),
|
|
11
|
+
throwable: true,
|
|
12
|
+
type: "message-return",
|
|
13
|
+
},
|
|
14
|
+
onHandlerSuccess: {
|
|
15
|
+
S: Symbol("handlerSuccess"),
|
|
16
|
+
throwable: false,
|
|
17
|
+
type: "message-return",
|
|
18
|
+
},
|
|
19
|
+
onHandlerTimeout: {
|
|
20
|
+
S: Symbol("handlerTimeout"),
|
|
21
|
+
throwable: false,
|
|
22
|
+
type: "boolean-return",
|
|
23
|
+
},
|
|
24
|
+
onHandlerError: {
|
|
25
|
+
S: Symbol("handlerError"),
|
|
26
|
+
throwable: false,
|
|
27
|
+
type: "boolean-return",
|
|
28
|
+
},
|
|
29
|
+
onSuccess: {
|
|
30
|
+
S: Symbol("success"),
|
|
31
|
+
throwable: false,
|
|
32
|
+
type: "boolean-return",
|
|
33
|
+
},
|
|
34
|
+
onError: {
|
|
35
|
+
S: Symbol("error"),
|
|
36
|
+
throwable: false,
|
|
37
|
+
type: "boolean-return",
|
|
38
|
+
},
|
|
39
|
+
onSQSError: {
|
|
40
|
+
S: Symbol("sqsError"),
|
|
41
|
+
throwable: false,
|
|
42
|
+
type: "void",
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
this.logger = logger;
|
|
46
|
+
this.hooks = {
|
|
47
|
+
[this.hookSymbols.onPoll.S]: [],
|
|
48
|
+
[this.hookSymbols.onMessage.S]: [],
|
|
49
|
+
[this.hookSymbols.onHandlerSuccess.S]: [],
|
|
50
|
+
[this.hookSymbols.onHandlerTimeout.S]: [],
|
|
51
|
+
[this.hookSymbols.onHandlerError.S]: [],
|
|
52
|
+
[this.hookSymbols.onSuccess.S]: [],
|
|
53
|
+
[this.hookSymbols.onError.S]: [],
|
|
54
|
+
[this.hookSymbols.onSQSError.S]: [],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
addHook(hook, fn) {
|
|
58
|
+
const hookSymbol = this.hookSymbols[hook];
|
|
59
|
+
if (!hookSymbol)
|
|
60
|
+
throw new Error(`Invalid hook ${hook}`);
|
|
61
|
+
this.hooks[hookSymbol.S]?.push(fn);
|
|
62
|
+
}
|
|
63
|
+
// biome-ignore lint/suspicious/noExplicitAny: args can be anything
|
|
64
|
+
async runHook(hook, ...args) {
|
|
65
|
+
const hookSymbol = this.hookSymbols[hook];
|
|
66
|
+
try {
|
|
67
|
+
if (!hookSymbol)
|
|
68
|
+
throw new Error(`Invalid hook ${hook}`);
|
|
69
|
+
switch (hookSymbol.type) {
|
|
70
|
+
case "boolean-return":
|
|
71
|
+
return await this.runHookWithBooleanReturn(hookSymbol, ...args);
|
|
72
|
+
case "message-return": {
|
|
73
|
+
const [message, ...someArgs] = args;
|
|
74
|
+
return await this.runHookWithMessageReturn(hookSymbol, message, ...someArgs);
|
|
75
|
+
}
|
|
76
|
+
case "void":
|
|
77
|
+
return await this.runHookWithVoidReturn(hookSymbol, ...args);
|
|
78
|
+
}
|
|
79
|
+
/* c8 ignore next 1 */
|
|
80
|
+
// biome-ignore lint/suspicious/noExplicitAny: error type is not important
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
error.message = `Error running hook ${hook}: ${error.message}`;
|
|
84
|
+
if (hookSymbol.throwable !== false)
|
|
85
|
+
throw error;
|
|
86
|
+
this.logger.error(error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async runHookWithVoidReturn(hookSymbol,
|
|
90
|
+
// biome-ignore lint/suspicious/noExplicitAny: args can be anything
|
|
91
|
+
...args) {
|
|
92
|
+
for (const fn of this.hooks[hookSymbol.S]) {
|
|
93
|
+
await fn(...args);
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
async runHookWithMessageReturn(hookSymbol, message,
|
|
98
|
+
// biome-ignore lint/suspicious/noExplicitAny: args can be anything
|
|
99
|
+
...args) {
|
|
100
|
+
for (const fn of this.hooks[hookSymbol.S]) {
|
|
101
|
+
const returnMessage = await fn(message, ...args);
|
|
102
|
+
if (returnMessage) {
|
|
103
|
+
message = returnMessage;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return message;
|
|
107
|
+
}
|
|
108
|
+
async runHookWithBooleanReturn(hookSymbol,
|
|
109
|
+
// biome-ignore lint/suspicious/noExplicitAny: args can be anything
|
|
110
|
+
...args) {
|
|
111
|
+
for (const fn of this.hooks[hookSymbol.S]) {
|
|
112
|
+
const continueLoop = await fn(...args);
|
|
113
|
+
if (continueLoop === false) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Signer, SignerOptions } from "@fgiova/aws-signature";
|
|
2
|
+
import { type Message, MiniSQSClient } from "@fgiova/mini-sqs-client";
|
|
3
|
+
import type { Pool } from "undici";
|
|
4
|
+
import { type HookName } from "./hooks.mjs";
|
|
5
|
+
import type { Logger } from "./logger.mjs";
|
|
6
|
+
type HandlerOptions = {
|
|
7
|
+
deleteMessage?: boolean;
|
|
8
|
+
extendVisibilityTimeout?: boolean;
|
|
9
|
+
executionTimeout?: number;
|
|
10
|
+
parallelExecution?: boolean;
|
|
11
|
+
};
|
|
12
|
+
type ClientOptions = {
|
|
13
|
+
sqsClient?: MiniSQSClient;
|
|
14
|
+
endpoint?: string;
|
|
15
|
+
undiciOptions?: Pool.Options;
|
|
16
|
+
signer?: Signer | SignerOptions;
|
|
17
|
+
destroySigner?: boolean;
|
|
18
|
+
};
|
|
19
|
+
type ConsumerOptions = {
|
|
20
|
+
visibilityTimeout?: number;
|
|
21
|
+
waitTimeSeconds?: number;
|
|
22
|
+
itemsPerRequest?: number;
|
|
23
|
+
messageAttributeNames?: string[];
|
|
24
|
+
attributeNames?: string[];
|
|
25
|
+
};
|
|
26
|
+
type hookReturnMessages = Promise<Message[]> | Message[] | Promise<void> | void | undefined;
|
|
27
|
+
type hookReturnMessage = Promise<Message> | Message | Promise<void> | void | undefined;
|
|
28
|
+
type hookReturnBoolean = Promise<boolean> | boolean | Promise<void> | void | undefined;
|
|
29
|
+
export type HooksOptions = {
|
|
30
|
+
onPoll?: (messages: Message[]) => hookReturnMessages;
|
|
31
|
+
onMessage?: (message: Message) => hookReturnMessage;
|
|
32
|
+
onHandlerSuccess?: (message: Message) => hookReturnMessage;
|
|
33
|
+
onHandlerTimeout?: (message: Message) => hookReturnBoolean;
|
|
34
|
+
onHandlerError?: (message: Message, error: Error) => hookReturnBoolean;
|
|
35
|
+
onSuccess?: (message: Message) => hookReturnBoolean;
|
|
36
|
+
onError?: (hook: HookName, message: Message, error: Error) => hookReturnBoolean;
|
|
37
|
+
onSQSError?: (error: Error, message?: Message) => Promise<void>;
|
|
38
|
+
};
|
|
39
|
+
export type SQSConsumerOptions = {
|
|
40
|
+
queueARN: string;
|
|
41
|
+
handler: (message: Message) => Promise<any>;
|
|
42
|
+
logger?: Logger;
|
|
43
|
+
autostart?: boolean;
|
|
44
|
+
handlerOptions?: HandlerOptions;
|
|
45
|
+
clientOptions?: ClientOptions;
|
|
46
|
+
consumerOptions?: ConsumerOptions;
|
|
47
|
+
hooks?: HooksOptions;
|
|
48
|
+
};
|
|
49
|
+
export declare class SQSConsumer {
|
|
50
|
+
private readonly queueARN;
|
|
51
|
+
private readonly handlerOptions;
|
|
52
|
+
private readonly clientOptions;
|
|
53
|
+
private readonly consumerOptions;
|
|
54
|
+
private readonly sqsClient;
|
|
55
|
+
private readonly hooks;
|
|
56
|
+
private readonly messageHandler;
|
|
57
|
+
private messagesOnFly;
|
|
58
|
+
private running;
|
|
59
|
+
private polling;
|
|
60
|
+
private destroyed;
|
|
61
|
+
private readonly logger;
|
|
62
|
+
constructor(options: SQSConsumerOptions);
|
|
63
|
+
private runOnMessageHook;
|
|
64
|
+
private runHandler;
|
|
65
|
+
private haExtendVisibilityTimeout;
|
|
66
|
+
private deleteMessages;
|
|
67
|
+
private pollMessages;
|
|
68
|
+
start(): Promise<void>;
|
|
69
|
+
stop(destroy?: boolean): Promise<void>;
|
|
70
|
+
get isRunning(): boolean;
|
|
71
|
+
addHook(hookName: "onPoll", value: (messages: Message[]) => Promise<Message[]>): this;
|
|
72
|
+
addHook(hookName: "onMessage", value: (message: Message) => Promise<Message>): this;
|
|
73
|
+
addHook(hookName: "onHandlerSuccess", value: (message: Message) => Promise<Message>): this;
|
|
74
|
+
addHook(hookName: "onHandlerTimeout", value: (message: Message) => Promise<boolean>): this;
|
|
75
|
+
addHook(hookName: "onHandlerError", value: (message: Message, error: Error) => Promise<boolean>): this;
|
|
76
|
+
addHook(hookName: "onSuccess", value: (message: Message) => Promise<boolean>): this;
|
|
77
|
+
addHook(hookName: "onError", value: (hook: HookName, message: Message, error: Error) => Promise<boolean>): this;
|
|
78
|
+
addHook(hookName: "onSQSError", value: (error: Error, message?: Message) => Promise<void>): this;
|
|
79
|
+
addHook(hookName: HookName, value: Function): this;
|
|
80
|
+
}
|
|
81
|
+
export {};
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/** biome-ignore-all lint/complexity/noBannedTypes: Function required as generic */
|
|
2
|
+
import { setTimeout as setTimeoutAsync } from "node:timers/promises";
|
|
3
|
+
import { MiniSQSClient } from "@fgiova/mini-sqs-client";
|
|
4
|
+
// @ts-expect-error
|
|
5
|
+
import { Unpromise } from "@watchable/unpromise";
|
|
6
|
+
// @ts-expect-error
|
|
7
|
+
import pMap from "p-map";
|
|
8
|
+
import { TimeoutError } from "./errors.mjs";
|
|
9
|
+
import { Hooks } from "./hooks.mjs";
|
|
10
|
+
export class SQSConsumer {
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.messagesOnFly = 0;
|
|
13
|
+
this.running = false;
|
|
14
|
+
this.polling = false;
|
|
15
|
+
this.destroyed = false;
|
|
16
|
+
if (!options.queueARN)
|
|
17
|
+
throw new Error("queueARN is required");
|
|
18
|
+
if (!options.handler || typeof options.handler !== "function")
|
|
19
|
+
throw new Error("handler is required and must be a function");
|
|
20
|
+
this.logger = options.logger ?? console;
|
|
21
|
+
this.hooks = new Hooks(this.logger);
|
|
22
|
+
this.queueARN = options.queueARN;
|
|
23
|
+
const region = this.queueARN.split(":").reverse()[2];
|
|
24
|
+
this.clientOptions = {
|
|
25
|
+
destroySigner: options.clientOptions?.destroySigner ?? true,
|
|
26
|
+
};
|
|
27
|
+
this.handlerOptions = {
|
|
28
|
+
deleteMessage: options.handlerOptions?.deleteMessage ?? true,
|
|
29
|
+
extendVisibilityTimeout: options.handlerOptions?.extendVisibilityTimeout ?? true,
|
|
30
|
+
executionTimeout: options.handlerOptions?.executionTimeout ?? 30000,
|
|
31
|
+
parallelExecution: options.handlerOptions?.parallelExecution ?? true,
|
|
32
|
+
};
|
|
33
|
+
this.consumerOptions = {
|
|
34
|
+
visibilityTimeout: options.consumerOptions?.visibilityTimeout ?? 30,
|
|
35
|
+
waitTimeSeconds: options.consumerOptions?.waitTimeSeconds ?? 20,
|
|
36
|
+
itemsPerRequest: options.consumerOptions?.itemsPerRequest ?? 10,
|
|
37
|
+
messageAttributeNames: options.consumerOptions?.messageAttributeNames ?? [],
|
|
38
|
+
attributeNames: options.consumerOptions?.attributeNames ?? [],
|
|
39
|
+
};
|
|
40
|
+
this.sqsClient =
|
|
41
|
+
options.clientOptions?.sqsClient ??
|
|
42
|
+
new MiniSQSClient(region, options.clientOptions?.endpoint, options.clientOptions?.undiciOptions, options.clientOptions?.signer);
|
|
43
|
+
if (options.hooks) {
|
|
44
|
+
for (const [key, value] of Object.entries(options.hooks)) {
|
|
45
|
+
if (typeof value !== "function")
|
|
46
|
+
throw new Error(`${key} must be a function`);
|
|
47
|
+
this.addHook(key, value);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
this.messageHandler = options.handler;
|
|
51
|
+
if (options.autostart !== false) {
|
|
52
|
+
void this.start();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async runOnMessageHook(message) {
|
|
56
|
+
try {
|
|
57
|
+
await this.hooks.runHook("onMessage", message);
|
|
58
|
+
const result = await this.runHandler(this.messageHandler, message);
|
|
59
|
+
await this.hooks.runHook("onSuccess", message);
|
|
60
|
+
return {
|
|
61
|
+
message,
|
|
62
|
+
result,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
await this.hooks.runHook("onError", "onMessage", message, e);
|
|
67
|
+
return {
|
|
68
|
+
message,
|
|
69
|
+
error: e,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async runHandler(handler, message) {
|
|
74
|
+
try {
|
|
75
|
+
let handlerResult;
|
|
76
|
+
if (this.handlerOptions.executionTimeout) {
|
|
77
|
+
const response = await Unpromise.race([
|
|
78
|
+
handler(message),
|
|
79
|
+
new Promise((_resolve, reject) => {
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
reject(new TimeoutError("Handler execution timed out"));
|
|
82
|
+
}, this.handlerOptions.executionTimeout);
|
|
83
|
+
}),
|
|
84
|
+
]);
|
|
85
|
+
handlerResult = response;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
handlerResult = await handler(message);
|
|
89
|
+
}
|
|
90
|
+
await this.hooks.runHook("onHandlerSuccess", message);
|
|
91
|
+
return handlerResult;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (error instanceof TimeoutError) {
|
|
95
|
+
await this.hooks.runHook("onHandlerTimeout", message);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
await this.hooks.runHook("onHandlerError", message, error);
|
|
99
|
+
}
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
haExtendVisibilityTimeout(messages, visibilityTimeout) {
|
|
104
|
+
if (this.handlerOptions.extendVisibilityTimeout !== false) {
|
|
105
|
+
const processingMessages = messages.map(
|
|
106
|
+
// biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
|
|
107
|
+
(message) => message.ReceiptHandle);
|
|
108
|
+
return setInterval(async () => {
|
|
109
|
+
try {
|
|
110
|
+
await this.sqsClient.changeMessageVisibilityBatch(this.queueARN, processingMessages, visibilityTimeout);
|
|
111
|
+
} /* c8 ignore next 3 */
|
|
112
|
+
catch (e) {
|
|
113
|
+
await this.hooks.runHook("onSQSError", e);
|
|
114
|
+
}
|
|
115
|
+
}, visibilityTimeout * 1000 - 5);
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
async deleteMessages(messagesResults) {
|
|
120
|
+
const candidatesToDelete = [];
|
|
121
|
+
const candidateToRelease = [];
|
|
122
|
+
for (const result of messagesResults) {
|
|
123
|
+
if (result.result && this.handlerOptions.deleteMessage !== false) {
|
|
124
|
+
// biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
|
|
125
|
+
candidatesToDelete.push(result.message.ReceiptHandle);
|
|
126
|
+
}
|
|
127
|
+
else if (result.error) {
|
|
128
|
+
// biome-ignore lint/style/noNonNullAssertion: ReceiptHandle must be present here
|
|
129
|
+
candidateToRelease.push(result.message.ReceiptHandle);
|
|
130
|
+
}
|
|
131
|
+
if (candidatesToDelete.length)
|
|
132
|
+
await this.sqsClient.deleteMessageBatch(this.queueARN, candidatesToDelete);
|
|
133
|
+
if (candidateToRelease.length)
|
|
134
|
+
await this.sqsClient.changeMessageVisibilityBatch(this.queueARN, candidateToRelease, 0);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async pollMessages() {
|
|
138
|
+
if (!this.running)
|
|
139
|
+
return;
|
|
140
|
+
try {
|
|
141
|
+
this.polling = true;
|
|
142
|
+
/* c8 ignore next 1 */
|
|
143
|
+
const visibilityTimeout = this.consumerOptions.visibilityTimeout ?? 30;
|
|
144
|
+
const messagesResult = await this.sqsClient.receiveMessage(this.queueARN, {
|
|
145
|
+
WaitTimeSeconds: this.consumerOptions.waitTimeSeconds,
|
|
146
|
+
MaxNumberOfMessages: this.consumerOptions.itemsPerRequest,
|
|
147
|
+
VisibilityTimeout: visibilityTimeout,
|
|
148
|
+
MessageAttributeNames: this.consumerOptions.messageAttributeNames,
|
|
149
|
+
AttributeNames: this.consumerOptions.attributeNames,
|
|
150
|
+
});
|
|
151
|
+
/* c8 ignore next 1 */
|
|
152
|
+
const messages = messagesResult.Messages ?? [];
|
|
153
|
+
const totalMessages = messages.length;
|
|
154
|
+
this.messagesOnFly += totalMessages;
|
|
155
|
+
await this.hooks.runHook("onPoll", messages);
|
|
156
|
+
if (messages.length) {
|
|
157
|
+
const haTimeout = this.haExtendVisibilityTimeout(messages, visibilityTimeout);
|
|
158
|
+
this.messagesOnFly += messages.length - totalMessages;
|
|
159
|
+
if (this.handlerOptions.parallelExecution !== false) {
|
|
160
|
+
const results = await pMap(messages, async (message) => {
|
|
161
|
+
return await this.runOnMessageHook(message);
|
|
162
|
+
}, {
|
|
163
|
+
concurrency: 10,
|
|
164
|
+
stopOnError: false,
|
|
165
|
+
});
|
|
166
|
+
await this.deleteMessages(results);
|
|
167
|
+
this.messagesOnFly -= messages.length;
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
const handlingMessages = [...messages];
|
|
171
|
+
for (let i = 0; i < handlingMessages.length; i++) {
|
|
172
|
+
const result = await this.runOnMessageHook(handlingMessages[i]);
|
|
173
|
+
await this.deleteMessages([result]);
|
|
174
|
+
messages.splice(i, 1);
|
|
175
|
+
this.messagesOnFly -= 1;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (haTimeout)
|
|
179
|
+
clearInterval(haTimeout);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
this.messagesOnFly -= totalMessages;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch (e) {
|
|
186
|
+
await this.hooks.runHook("onSQSError", e);
|
|
187
|
+
}
|
|
188
|
+
this.polling = false;
|
|
189
|
+
await this.pollMessages();
|
|
190
|
+
}
|
|
191
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
192
|
+
async start() {
|
|
193
|
+
if (this.running)
|
|
194
|
+
throw new Error("Consumer is already running");
|
|
195
|
+
if (this.destroyed)
|
|
196
|
+
throw new Error("Consumer is destroyed");
|
|
197
|
+
this.running = true;
|
|
198
|
+
void this.pollMessages();
|
|
199
|
+
}
|
|
200
|
+
async stop(destroy = false) {
|
|
201
|
+
if (!this.running)
|
|
202
|
+
throw new Error("Consumer is not running");
|
|
203
|
+
this.running = false;
|
|
204
|
+
while (this.messagesOnFly > 0 || this.polling) {
|
|
205
|
+
await setTimeoutAsync(500);
|
|
206
|
+
}
|
|
207
|
+
if (destroy) {
|
|
208
|
+
await this.sqsClient.destroy(this.clientOptions.destroySigner);
|
|
209
|
+
this.destroyed = true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/* c8 ignore next 3 */
|
|
213
|
+
get isRunning() {
|
|
214
|
+
return this.running;
|
|
215
|
+
}
|
|
216
|
+
addHook(hookName, value) {
|
|
217
|
+
this.hooks.addHook(hookName, value);
|
|
218
|
+
return this;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a generic logger that could be a simple console, pino etc.
|
|
3
|
+
*/
|
|
4
|
+
/** biome-ignore-all lint/suspicious/noExplicitAny: leave message logger as any */
|
|
5
|
+
export interface Logger {
|
|
6
|
+
trace(message?: any, ...optionalParams: any[]): void;
|
|
7
|
+
debug(message?: any, ...optionalParams: any[]): void;
|
|
8
|
+
info(message?: any, ...optionalParams: any[]): void;
|
|
9
|
+
warn(message?: any, ...optionalParams: any[]): void;
|
|
10
|
+
error(message?: any, ...optionalParams: any[]): void;
|
|
11
|
+
[x: string]: any;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fgiova/sqs-consumer",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"main": "dist/index.js",
|
|
3
|
+
"version": "2.1.1",
|
|
4
|
+
"main": "./dist/index.js",
|
|
5
|
+
"types": "./dist/index.d.ts",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"import": {
|
|
9
|
+
"types": "./dist/esm/index.d.mts",
|
|
10
|
+
"default": "./dist/esm/index.mjs"
|
|
11
|
+
},
|
|
12
|
+
"require": {
|
|
13
|
+
"types": "./dist/cjs/index.d.ts",
|
|
14
|
+
"default": "./dist/cjs/index.js"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
5
18
|
"files": [
|
|
6
19
|
"dist"
|
|
7
20
|
],
|
|
8
21
|
"scripts": {
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
22
|
+
"clean": "rimraf dist",
|
|
23
|
+
"preBuild": "npm run clean",
|
|
24
|
+
"build": "esmBuilder",
|
|
25
|
+
"lint": "biome ci ./src ./test",
|
|
26
|
+
"fix": "biome check --write ./src ./test",
|
|
12
27
|
"test": "tap",
|
|
13
28
|
"test:debug": "tap --only --timeout=0",
|
|
14
29
|
"test:coverage": "tap --coverage-report=lcovonly --coverage-report=text",
|
|
@@ -41,26 +56,25 @@
|
|
|
41
56
|
]
|
|
42
57
|
},
|
|
43
58
|
"dependencies": {
|
|
44
|
-
"@fgiova/mini-sqs-client": "^
|
|
59
|
+
"@fgiova/mini-sqs-client": "^3.2.0",
|
|
45
60
|
"@watchable/unpromise": "^1.0.2",
|
|
46
|
-
"p-map": "^
|
|
61
|
+
"p-map": "^7.0.3"
|
|
47
62
|
},
|
|
48
63
|
"peerDependencies": {
|
|
49
|
-
"@fgiova/aws-signature": "^
|
|
64
|
+
"@fgiova/aws-signature": "^3.1.0"
|
|
50
65
|
},
|
|
51
66
|
"devDependencies": {
|
|
52
67
|
"@aws-sdk/client-sqs": "^3.758.0",
|
|
68
|
+
"@biomejs/biome": "2.2.2",
|
|
69
|
+
"@fgiova/cjs-esm-ts-builder": "^1.2.0",
|
|
53
70
|
"@semantic-release/changelog": "^6.0.3",
|
|
54
71
|
"@semantic-release/git": "^10.0.1",
|
|
72
|
+
"@tsconfig/node20": "^20.1.4",
|
|
55
73
|
"@types/node": "^20.17.23",
|
|
56
|
-
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
|
57
|
-
"@typescript-eslint/parser": "^6.21.0",
|
|
58
74
|
"conventional-changelog-conventionalcommits": "^8.0.0",
|
|
59
|
-
"eslint": "^8.57.1",
|
|
60
|
-
"eslint-plugin-import": "^2.31.0",
|
|
61
75
|
"semantic-release": "^24.2.3",
|
|
62
76
|
"tap": "^21.1.0",
|
|
63
|
-
"testcontainers": "^
|
|
77
|
+
"testcontainers": "^11.5.1",
|
|
64
78
|
"ts-node": "^10.9.2",
|
|
65
79
|
"typescript": "^5.8.2"
|
|
66
80
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|