@fgiova/sqs-consumer 3.0.0 → 3.1.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 +20 -0
- package/dist/cjs/context.d.ts +7 -0
- package/dist/cjs/context.js +9 -0
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +14 -4
- package/dist/esm/context.d.mts +7 -0
- package/dist/esm/context.mjs +5 -0
- package/dist/esm/index.d.mts +1 -1
- package/dist/esm/index.mjs +12 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -29,6 +29,25 @@ const consumer = new SQSConsumer({
|
|
|
29
29
|
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
## Handler cancellation on timeout
|
|
33
|
+
|
|
34
|
+
When `handlerOptions.executionTimeout` triggers, the consumer aborts the in-flight handler via an `AbortSignal` exposed through `AsyncLocalStorage`. The handler does not need to receive the signal as an argument — it is retrieved on demand using the `getAbortSignal` helper. Pass it to any cancellable async API (`fetch`, `undici`, timers, streams) to release resources promptly and avoid zombie handlers retaining memory after a timeout.
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { SQSConsumer, getAbortSignal } from '@fgiova/sqs-consumer'
|
|
38
|
+
|
|
39
|
+
const consumer = new SQSConsumer({
|
|
40
|
+
queueARN: "arn:aws:sqs:eu-central-1:000000000000:test-queue-hooks",
|
|
41
|
+
handler: async (message) => {
|
|
42
|
+
const signal = getAbortSignal();
|
|
43
|
+
const res = await fetch("https://api.example.com/work", { signal });
|
|
44
|
+
return res.json();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`getAbortSignal()` returns `undefined` when called outside a handler execution context. Handlers that ignore it keep working unchanged.
|
|
50
|
+
|
|
32
51
|
## Options
|
|
33
52
|
| Option | Type | Default | Description |
|
|
34
53
|
|-----------------|-------------------------------------|---------|----------------------------------------------------|
|
|
@@ -86,6 +105,7 @@ SQSConsumer(options: SQSConsumerOptions)
|
|
|
86
105
|
SQSConsumer.start(): Promise<void>
|
|
87
106
|
SQSConsumer.stop(destroyConsumer = false): Promise<void>
|
|
88
107
|
SQSConsumer.addHook<H extends HookName>(hookName: H, value: HookCallback<H>): this
|
|
108
|
+
getAbortSignal(): AbortSignal | undefined
|
|
89
109
|
```
|
|
90
110
|
|
|
91
111
|
## License
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handlerContext = void 0;
|
|
4
|
+
exports.getAbortSignal = getAbortSignal;
|
|
5
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
6
|
+
exports.handlerContext = new node_async_hooks_1.AsyncLocalStorage();
|
|
7
|
+
function getAbortSignal() {
|
|
8
|
+
return exports.handlerContext.getStore()?.signal;
|
|
9
|
+
}
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { type Message, MiniSQSClient } from "@fgiova/mini-sqs-client";
|
|
|
3
3
|
import type { Pool } from "undici";
|
|
4
4
|
import { type HookCallback, type HookName } from "./hooks";
|
|
5
5
|
import type { Logger } from "./logger";
|
|
6
|
+
export { getAbortSignal } from "./context";
|
|
6
7
|
type HandlerOptions = {
|
|
7
8
|
deleteMessage?: boolean;
|
|
8
9
|
extendVisibilityTimeout?: boolean;
|
|
@@ -61,4 +62,3 @@ export declare class SQSConsumer {
|
|
|
61
62
|
get isRunning(): boolean;
|
|
62
63
|
addHook<H extends HookName>(hookName: H, value: HookCallback<H>): this;
|
|
63
64
|
}
|
|
64
|
-
export {};
|
package/dist/cjs/index.js
CHANGED
|
@@ -3,15 +3,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.SQSConsumer = void 0;
|
|
6
|
+
exports.SQSConsumer = exports.getAbortSignal = void 0;
|
|
7
7
|
const promises_1 = require("node:timers/promises");
|
|
8
8
|
const mini_sqs_client_1 = require("@fgiova/mini-sqs-client");
|
|
9
9
|
// @ts-expect-error
|
|
10
10
|
const unpromise_1 = require("@watchable/unpromise");
|
|
11
11
|
// @ts-expect-error
|
|
12
12
|
const p_map_1 = __importDefault(require("p-map"));
|
|
13
|
+
const context_1 = require("./context");
|
|
13
14
|
const errors_1 = require("./errors");
|
|
14
15
|
const hooks_1 = require("./hooks");
|
|
16
|
+
var context_2 = require("./context");
|
|
17
|
+
Object.defineProperty(exports, "getAbortSignal", { enumerable: true, get: function () { return context_2.getAbortSignal; } });
|
|
15
18
|
class SQSConsumer {
|
|
16
19
|
constructor(options) {
|
|
17
20
|
this.messagesOnFly = 0;
|
|
@@ -78,20 +81,27 @@ class SQSConsumer {
|
|
|
78
81
|
}
|
|
79
82
|
async runHandler(handler, message) {
|
|
80
83
|
let timeoutId = null;
|
|
84
|
+
const abortController = new AbortController();
|
|
81
85
|
try {
|
|
82
86
|
let handlerResult;
|
|
83
87
|
if (this.handlerOptions.executionTimeout) {
|
|
88
|
+
const handlerPromise = context_1.handlerContext.run({ signal: abortController.signal }, () => handler(message));
|
|
89
|
+
// Swallow late rejection of the loser when timeout wins the race,
|
|
90
|
+
// otherwise it surfaces as an unhandledRejection.
|
|
91
|
+
handlerPromise.catch(() => { });
|
|
84
92
|
handlerResult = await unpromise_1.Unpromise.race([
|
|
85
|
-
|
|
93
|
+
handlerPromise,
|
|
86
94
|
new Promise((_resolve, reject) => {
|
|
87
95
|
timeoutId = setTimeout(() => {
|
|
88
|
-
|
|
96
|
+
const err = new errors_1.TimeoutError("Handler execution timed out");
|
|
97
|
+
abortController.abort(err);
|
|
98
|
+
reject(err);
|
|
89
99
|
}, this.handlerOptions.executionTimeout);
|
|
90
100
|
}),
|
|
91
101
|
]);
|
|
92
102
|
}
|
|
93
103
|
else {
|
|
94
|
-
handlerResult = await handler(message);
|
|
104
|
+
handlerResult = await context_1.handlerContext.run({ signal: abortController.signal }, () => handler(message));
|
|
95
105
|
}
|
|
96
106
|
await this.hooks.runHook("onHandlerSuccess", message);
|
|
97
107
|
return handlerResult;
|
package/dist/esm/index.d.mts
CHANGED
|
@@ -3,6 +3,7 @@ import { type Message, MiniSQSClient } from "@fgiova/mini-sqs-client";
|
|
|
3
3
|
import type { Pool } from "undici";
|
|
4
4
|
import { type HookCallback, type HookName } from "./hooks.mjs";
|
|
5
5
|
import type { Logger } from "./logger.mjs";
|
|
6
|
+
export { getAbortSignal } from "./context.mjs";
|
|
6
7
|
type HandlerOptions = {
|
|
7
8
|
deleteMessage?: boolean;
|
|
8
9
|
extendVisibilityTimeout?: boolean;
|
|
@@ -61,4 +62,3 @@ export declare class SQSConsumer {
|
|
|
61
62
|
get isRunning(): boolean;
|
|
62
63
|
addHook<H extends HookName>(hookName: H, value: HookCallback<H>): this;
|
|
63
64
|
}
|
|
64
|
-
export {};
|
package/dist/esm/index.mjs
CHANGED
|
@@ -4,8 +4,10 @@ import { MiniSQSClient } from "@fgiova/mini-sqs-client";
|
|
|
4
4
|
import { Unpromise } from "@watchable/unpromise";
|
|
5
5
|
// @ts-expect-error
|
|
6
6
|
import pMap from "p-map";
|
|
7
|
+
import { handlerContext } from "./context.mjs";
|
|
7
8
|
import { TimeoutError } from "./errors.mjs";
|
|
8
9
|
import { Hooks } from "./hooks.mjs";
|
|
10
|
+
export { getAbortSignal } from "./context.mjs";
|
|
9
11
|
export class SQSConsumer {
|
|
10
12
|
constructor(options) {
|
|
11
13
|
this.messagesOnFly = 0;
|
|
@@ -72,20 +74,27 @@ export class SQSConsumer {
|
|
|
72
74
|
}
|
|
73
75
|
async runHandler(handler, message) {
|
|
74
76
|
let timeoutId = null;
|
|
77
|
+
const abortController = new AbortController();
|
|
75
78
|
try {
|
|
76
79
|
let handlerResult;
|
|
77
80
|
if (this.handlerOptions.executionTimeout) {
|
|
81
|
+
const handlerPromise = handlerContext.run({ signal: abortController.signal }, () => handler(message));
|
|
82
|
+
// Swallow late rejection of the loser when timeout wins the race,
|
|
83
|
+
// otherwise it surfaces as an unhandledRejection.
|
|
84
|
+
handlerPromise.catch(() => { });
|
|
78
85
|
handlerResult = await Unpromise.race([
|
|
79
|
-
|
|
86
|
+
handlerPromise,
|
|
80
87
|
new Promise((_resolve, reject) => {
|
|
81
88
|
timeoutId = setTimeout(() => {
|
|
82
|
-
|
|
89
|
+
const err = new TimeoutError("Handler execution timed out");
|
|
90
|
+
abortController.abort(err);
|
|
91
|
+
reject(err);
|
|
83
92
|
}, this.handlerOptions.executionTimeout);
|
|
84
93
|
}),
|
|
85
94
|
]);
|
|
86
95
|
}
|
|
87
96
|
else {
|
|
88
|
-
handlerResult = await handler(message);
|
|
97
|
+
handlerResult = await handlerContext.run({ signal: abortController.signal }, () => handler(message));
|
|
89
98
|
}
|
|
90
99
|
await this.hooks.runHook("onHandlerSuccess", message);
|
|
91
100
|
return handlerResult;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fgiova/sqs-consumer",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"main": "./dist/cjs/index.js",
|
|
5
5
|
"types": "./dist/cjs/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
]
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@fgiova/mini-sqs-client": "^
|
|
59
|
+
"@fgiova/mini-sqs-client": "^5.0.0",
|
|
60
60
|
"@watchable/unpromise": "^1.0.2",
|
|
61
61
|
"p-map": "^7.0.3"
|
|
62
62
|
},
|