@dotpress/events 0.0.0-stage → 2.0.0-alpha.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/LICENSE +21 -0
- package/README.md +707 -2
- package/dist/aws-sqs.d.ts +127 -0
- package/dist/aws-sqs.js +440 -0
- package/dist/aws-sqs.js.map +1 -0
- package/dist/azure-service-bus.d.ts +99 -0
- package/dist/azure-service-bus.js +320 -0
- package/dist/azure-service-bus.js.map +1 -0
- package/dist/bullmq.d.ts +92 -0
- package/dist/bullmq.js +169 -0
- package/dist/bullmq.js.map +1 -0
- package/dist/bus-Bei_kN-e.d.ts +137 -0
- package/dist/chunk-E4NMMZMY.js +24 -0
- package/dist/chunk-E4NMMZMY.js.map +1 -0
- package/dist/chunk-NCQ4ZTNB.js +50 -0
- package/dist/chunk-NCQ4ZTNB.js.map +1 -0
- package/dist/cli.js +202 -0
- package/dist/cli.js.map +1 -0
- package/dist/cloudflare-queues.d.ts +120 -0
- package/dist/cloudflare-queues.js +333 -0
- package/dist/cloudflare-queues.js.map +1 -0
- package/dist/google-pubsub.d.ts +111 -0
- package/dist/google-pubsub.js +420 -0
- package/dist/google-pubsub.js.map +1 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.js +701 -0
- package/dist/index.js.map +1 -0
- package/dist/pg-boss.d.ts +79 -0
- package/dist/pg-boss.js +189 -0
- package/dist/pg-boss.js.map +1 -0
- package/dist/plugin.d.ts +40 -0
- package/dist/plugin.js +27 -0
- package/dist/plugin.js.map +1 -0
- package/dist/transport-BtNR1ucF.d.ts +273 -0
- package/package.json +139 -3
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { SQSClientConfig } from '@aws-sdk/client-sqs';
|
|
2
|
+
import { EventsLogger, Envelope, Worker, Source, Transport } from './index.js';
|
|
3
|
+
import 'zod';
|
|
4
|
+
|
|
5
|
+
/** An SQS client, or any object with the same `send()` method. */
|
|
6
|
+
interface SqsClient {
|
|
7
|
+
send(command: object, options?: {
|
|
8
|
+
abortSignal?: AbortSignal;
|
|
9
|
+
}): Promise<unknown>;
|
|
10
|
+
}
|
|
11
|
+
/** Client options shared by the transport, the handler and the source. */
|
|
12
|
+
interface SqsClientOptions {
|
|
13
|
+
/** Existing client. Defaults to a client created with `clientConfig`. */
|
|
14
|
+
client?: SqsClient | undefined;
|
|
15
|
+
/** Configuration of the created client: `region`, `credentials`… */
|
|
16
|
+
clientConfig?: SQSClientConfig | undefined;
|
|
17
|
+
}
|
|
18
|
+
/** Options of `createSqsTransport()`. */
|
|
19
|
+
interface SqsTransportOptions extends SqsClientOptions {
|
|
20
|
+
/** URL of the queue. A `.fifo` URL makes a FIFO queue. */
|
|
21
|
+
queueUrl: string;
|
|
22
|
+
/**
|
|
23
|
+
* FIFO queues: message group of a message, ordered within its group.
|
|
24
|
+
* Defaults to the listener: messages of a listener are handled in order.
|
|
25
|
+
*/
|
|
26
|
+
messageGroupId?: ((envelope: Envelope) => string) | undefined;
|
|
27
|
+
}
|
|
28
|
+
/** How failed messages are handled, by the Lambda handler and the source. */
|
|
29
|
+
interface SqsFailureOptions extends SqsClientOptions {
|
|
30
|
+
/**
|
|
31
|
+
* Dead-letter queue of the queue: messages that can never be handled
|
|
32
|
+
* (invalid payload, unknown listener) are copied there at once, instead
|
|
33
|
+
* of failing until the `maxReceiveCount` of the queue.
|
|
34
|
+
*/
|
|
35
|
+
deadLetterQueueUrl?: string | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Delay before a failed message is delivered again, in seconds, given its
|
|
38
|
+
* attempt number. Defaults to the visibility timeout of the queue.
|
|
39
|
+
*/
|
|
40
|
+
retryDelay?: ((attempt: number) => number) | undefined;
|
|
41
|
+
logger?: EventsLogger | undefined;
|
|
42
|
+
}
|
|
43
|
+
/** Options of `createSqsHandler()`. */
|
|
44
|
+
interface SqsHandlerOptions extends SqsFailureOptions {
|
|
45
|
+
/**
|
|
46
|
+
* URL of the queue, for `retryDelay`. Defaults to the URL derived from
|
|
47
|
+
* the ARN of the records.
|
|
48
|
+
*/
|
|
49
|
+
queueUrl?: string | undefined;
|
|
50
|
+
}
|
|
51
|
+
/** Options of `createSqsSource()`. */
|
|
52
|
+
interface SqsSourceOptions extends SqsFailureOptions {
|
|
53
|
+
queueUrl: string;
|
|
54
|
+
/** Long polling, in seconds (0 to 20). Defaults to 20. */
|
|
55
|
+
waitTimeSeconds?: number | undefined;
|
|
56
|
+
/** Messages per request (1 to 10). Defaults to 10. */
|
|
57
|
+
batchSize?: number | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Visibility timeout of the received messages, in seconds. When set, the
|
|
60
|
+
* source extends it while a message is being handled, so that a long
|
|
61
|
+
* listener is not delivered twice. Defaults to the one of the queue.
|
|
62
|
+
*/
|
|
63
|
+
visibilityTimeout?: number | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* Warns at startup when the queue has no dead-letter queue (redrive
|
|
66
|
+
* policy). Defaults to true; needs `sqs:GetQueueAttributes`.
|
|
67
|
+
*/
|
|
68
|
+
checkRedrivePolicy?: boolean | undefined;
|
|
69
|
+
}
|
|
70
|
+
/** Record of an SQS event received by a Lambda function. */
|
|
71
|
+
interface SqsRecord {
|
|
72
|
+
readonly messageId: string;
|
|
73
|
+
readonly receiptHandle: string;
|
|
74
|
+
readonly body: string;
|
|
75
|
+
readonly attributes: {
|
|
76
|
+
readonly ApproximateReceiveCount?: string;
|
|
77
|
+
};
|
|
78
|
+
readonly eventSourceARN?: string;
|
|
79
|
+
}
|
|
80
|
+
/** SQS event received by a Lambda function. */
|
|
81
|
+
interface SqsEvent {
|
|
82
|
+
readonly Records: readonly SqsRecord[];
|
|
83
|
+
}
|
|
84
|
+
/** Response of the Lambda handler (`ReportBatchItemFailures`). */
|
|
85
|
+
interface SqsBatchResponse {
|
|
86
|
+
batchItemFailures: {
|
|
87
|
+
itemIdentifier: string;
|
|
88
|
+
}[];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Creates a transport that sends messages to an Amazon SQS queue, through
|
|
92
|
+
* the AWS SDK (`@aws-sdk/client-sqs`, peer dependency). Messages are sent
|
|
93
|
+
* in batches of 10; each one carries `event` and `listener` attributes.
|
|
94
|
+
*
|
|
95
|
+
* ```ts
|
|
96
|
+
* const transport = createSqsTransport({ queueUrl: process.env.EVENTS_QUEUE_URL! })
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
declare const createSqsTransport: (options: SqsTransportOptions) => Transport;
|
|
100
|
+
/** URL of the queue of an ARN (`arn:aws:sqs:region:account:name`). */
|
|
101
|
+
declare const queueUrlOf: (arn: string) => string | undefined;
|
|
102
|
+
/**
|
|
103
|
+
* Creates the handler of a Lambda function triggered by the SQS queue.
|
|
104
|
+
* Enable `ReportBatchItemFailures` on the event source mapping: only the
|
|
105
|
+
* failed messages are retried.
|
|
106
|
+
*
|
|
107
|
+
* ```ts
|
|
108
|
+
* export const handler = createSqsHandler(createWorker({ listeners }))
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
declare const createSqsHandler: (worker: Worker, options?: SqsHandlerOptions) => (event: SqsEvent) => Promise<SqsBatchResponse>;
|
|
112
|
+
/**
|
|
113
|
+
* Creates a source that reads the SQS queue by long polling, for
|
|
114
|
+
* `startWorker()` and `dotpress-events work`: a container, a VM, or your
|
|
115
|
+
* machine reading a development queue.
|
|
116
|
+
*
|
|
117
|
+
* ```ts
|
|
118
|
+
* export default {
|
|
119
|
+
* worker: createWorker({ listeners }),
|
|
120
|
+
* source: createSqsSource({ queueUrl: process.env.EVENTS_QUEUE_URL! }),
|
|
121
|
+
* }
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
declare const createSqsSource: (options: SqsSourceOptions) => Source;
|
|
125
|
+
|
|
126
|
+
export { createSqsHandler, createSqsSource, createSqsTransport, queueUrlOf };
|
|
127
|
+
export type { SqsBatchResponse, SqsClient, SqsClientOptions, SqsEvent, SqsFailureOptions, SqsHandlerOptions, SqsRecord, SqsSourceOptions, SqsTransportOptions };
|
package/dist/aws-sqs.js
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import {
|
|
2
|
+
checkLogger,
|
|
3
|
+
silentLogger
|
|
4
|
+
} from "./chunk-E4NMMZMY.js";
|
|
5
|
+
import {
|
|
6
|
+
PartialSendError
|
|
7
|
+
} from "./chunk-NCQ4ZTNB.js";
|
|
8
|
+
|
|
9
|
+
// src/transports/aws-sqs.ts
|
|
10
|
+
import {
|
|
11
|
+
ChangeMessageVisibilityCommand,
|
|
12
|
+
DeleteMessageBatchCommand,
|
|
13
|
+
GetQueueAttributesCommand,
|
|
14
|
+
ReceiveMessageCommand,
|
|
15
|
+
SendMessageBatchCommand,
|
|
16
|
+
SendMessageCommand,
|
|
17
|
+
SQSClient
|
|
18
|
+
} from "@aws-sdk/client-sqs";
|
|
19
|
+
var MAX_BATCH_ENTRIES = 10;
|
|
20
|
+
var MAX_MESSAGE_BYTES = 1048576;
|
|
21
|
+
var MAX_DELAY_SECONDS = 900;
|
|
22
|
+
var MAX_VISIBILITY_SECONDS = 43200;
|
|
23
|
+
var MAX_WAIT_SECONDS = 20;
|
|
24
|
+
var encoder = new TextEncoder();
|
|
25
|
+
var bytesOf = (text) => encoder.encode(text).byteLength;
|
|
26
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
27
|
+
var checkOptions = (label, options, allowed) => {
|
|
28
|
+
if (!isRecord(options)) {
|
|
29
|
+
throw new TypeError(`${label}: expected an object of options.`);
|
|
30
|
+
}
|
|
31
|
+
for (const key of Object.keys(options)) {
|
|
32
|
+
if (!allowed.includes(key)) {
|
|
33
|
+
throw new TypeError(`${label}: unknown option "${key}".`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return options;
|
|
37
|
+
};
|
|
38
|
+
var checkUrl = (label, key, value) => {
|
|
39
|
+
if (typeof value !== "string" || !/^https?:\/\/[^/]/.test(value)) {
|
|
40
|
+
throw new TypeError(`${label}: ${key} must be the URL of an SQS queue.`);
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
};
|
|
44
|
+
var checkInteger = (label, key, value, min, max) => {
|
|
45
|
+
if (value !== void 0 && (!Number.isInteger(value) || value < min || value > max)) {
|
|
46
|
+
throw new TypeError(
|
|
47
|
+
`${label}: ${key} must be an integer from ${min} to ${max}.`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return value;
|
|
51
|
+
};
|
|
52
|
+
var checkFunction = (label, key, value) => {
|
|
53
|
+
if (value !== void 0 && typeof value !== "function") {
|
|
54
|
+
throw new TypeError(`${label}: ${key} must be a function.`);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var clientOf = (label, values) => {
|
|
58
|
+
const { client, clientConfig } = values;
|
|
59
|
+
if (client !== void 0 && clientConfig !== void 0) {
|
|
60
|
+
throw new TypeError(`${label}: set client or clientConfig, not both.`);
|
|
61
|
+
}
|
|
62
|
+
if (client !== void 0 && typeof client?.send !== "function") {
|
|
63
|
+
throw new TypeError(`${label}: client must have a send() method.`);
|
|
64
|
+
}
|
|
65
|
+
let created;
|
|
66
|
+
return () => client ?? (created ??= new SQSClient(clientConfig ?? {}));
|
|
67
|
+
};
|
|
68
|
+
var attributesOf = (envelope) => ({
|
|
69
|
+
event: { DataType: "String", StringValue: envelope.event },
|
|
70
|
+
listener: { DataType: "String", StringValue: envelope.listener }
|
|
71
|
+
});
|
|
72
|
+
var sizeOf = (body, attributes) => Object.entries(attributes).reduce(
|
|
73
|
+
(total, [name, { DataType = "", StringValue = "" }]) => total + bytesOf(name) + bytesOf(DataType) + bytesOf(StringValue),
|
|
74
|
+
bytesOf(body)
|
|
75
|
+
);
|
|
76
|
+
var describe = (error) => error instanceof Error ? error.message : String(error);
|
|
77
|
+
var createSqsTransport = (options) => {
|
|
78
|
+
const label = "createSqsTransport()";
|
|
79
|
+
const values = checkOptions(label, options, [
|
|
80
|
+
"queueUrl",
|
|
81
|
+
"client",
|
|
82
|
+
"clientConfig",
|
|
83
|
+
"messageGroupId"
|
|
84
|
+
]);
|
|
85
|
+
const queueUrl = checkUrl(label, "queueUrl", values.queueUrl);
|
|
86
|
+
checkFunction(label, "messageGroupId", values.messageGroupId);
|
|
87
|
+
const client = clientOf(label, values);
|
|
88
|
+
const fifo = queueUrl.endsWith(".fifo");
|
|
89
|
+
const groupOf = values.messageGroupId ?? ((envelope) => envelope.listener);
|
|
90
|
+
return {
|
|
91
|
+
kind: "aws-sqs",
|
|
92
|
+
send: async (envelopes, { delay }) => {
|
|
93
|
+
if (delay !== void 0 && delay > MAX_DELAY_SECONDS) {
|
|
94
|
+
throw new RangeError(
|
|
95
|
+
`SQS accepts delays of at most ${MAX_DELAY_SECONDS} seconds, got ${delay}.`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
if (fifo && delay !== void 0 && delay > 0) {
|
|
99
|
+
throw new RangeError(
|
|
100
|
+
"SQS FIFO queues do not accept per-message delays."
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const failed = [];
|
|
104
|
+
const batches = [];
|
|
105
|
+
for (const envelope of envelopes) {
|
|
106
|
+
const body = JSON.stringify(envelope);
|
|
107
|
+
const attributes = attributesOf(envelope);
|
|
108
|
+
const size = sizeOf(body, attributes);
|
|
109
|
+
if (size > MAX_MESSAGE_BYTES) {
|
|
110
|
+
failed.push({
|
|
111
|
+
id: envelope.id,
|
|
112
|
+
error: new RangeError(
|
|
113
|
+
`SQS accepts messages of at most ${MAX_MESSAGE_BYTES} bytes (attributes included), got ${size}.`
|
|
114
|
+
)
|
|
115
|
+
});
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const entry = {
|
|
119
|
+
Id: envelope.id,
|
|
120
|
+
MessageBody: body,
|
|
121
|
+
MessageAttributes: attributes,
|
|
122
|
+
...fifo ? {
|
|
123
|
+
MessageGroupId: groupOf(envelope),
|
|
124
|
+
// Duplicates of a message are dropped within 5 minutes.
|
|
125
|
+
MessageDeduplicationId: envelope.id
|
|
126
|
+
} : delay === void 0 ? {} : { DelaySeconds: delay }
|
|
127
|
+
};
|
|
128
|
+
const last = batches.at(-1);
|
|
129
|
+
if (last && last.entries.length < MAX_BATCH_ENTRIES && last.size + size <= MAX_MESSAGE_BYTES) {
|
|
130
|
+
last.entries.push(entry);
|
|
131
|
+
last.size += size;
|
|
132
|
+
} else {
|
|
133
|
+
batches.push({ entries: [entry], size });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
for (const { entries } of batches) {
|
|
137
|
+
try {
|
|
138
|
+
const result = await client().send(
|
|
139
|
+
new SendMessageBatchCommand({
|
|
140
|
+
QueueUrl: queueUrl,
|
|
141
|
+
Entries: entries
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
for (const { Id, Code, Message } of result.Failed ?? []) {
|
|
145
|
+
failed.push({
|
|
146
|
+
id: Id,
|
|
147
|
+
error: new Error(`${Code}${Message ? `: ${Message}` : ""}`)
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
} catch (error) {
|
|
151
|
+
for (const { Id } of entries) failed.push({ id: Id, error });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (failed.length > 0) throw new PartialSendError(failed);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
var queueUrlOf = (arn) => {
|
|
159
|
+
const [, partition, , region, account, name] = arn.split(":");
|
|
160
|
+
if (!partition || !region || !account || !name) return void 0;
|
|
161
|
+
const domain = partition === "aws-cn" ? "amazonaws.com.cn" : "amazonaws.com";
|
|
162
|
+
return `https://sqs.${region}.${domain}/${account}/${name}`;
|
|
163
|
+
};
|
|
164
|
+
var settle = async (result, message, context) => {
|
|
165
|
+
if (result.status === "success") return true;
|
|
166
|
+
if (result.status === "dead" && context.deadLetterQueueUrl) {
|
|
167
|
+
try {
|
|
168
|
+
await context.client().send(
|
|
169
|
+
new SendMessageCommand({
|
|
170
|
+
QueueUrl: context.deadLetterQueueUrl,
|
|
171
|
+
MessageBody: message.body,
|
|
172
|
+
MessageAttributes: {
|
|
173
|
+
reason: { DataType: "String", StringValue: result.reason },
|
|
174
|
+
error: {
|
|
175
|
+
DataType: "String",
|
|
176
|
+
StringValue: describe(result.error).slice(0, 1024)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
);
|
|
181
|
+
return true;
|
|
182
|
+
} catch (error) {
|
|
183
|
+
context.logger.error(
|
|
184
|
+
{
|
|
185
|
+
deadLetterQueueUrl: context.deadLetterQueueUrl,
|
|
186
|
+
error: describe(error)
|
|
187
|
+
},
|
|
188
|
+
"Cannot copy the message to the dead-letter queue"
|
|
189
|
+
);
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (result.status === "retry" && context.retryDelay && message.queueUrl) {
|
|
194
|
+
const delay = Math.min(
|
|
195
|
+
MAX_VISIBILITY_SECONDS,
|
|
196
|
+
Math.max(0, Math.round(context.retryDelay(message.attempt)))
|
|
197
|
+
);
|
|
198
|
+
try {
|
|
199
|
+
await context.client().send(
|
|
200
|
+
new ChangeMessageVisibilityCommand({
|
|
201
|
+
QueueUrl: message.queueUrl,
|
|
202
|
+
ReceiptHandle: message.receiptHandle,
|
|
203
|
+
VisibilityTimeout: delay
|
|
204
|
+
})
|
|
205
|
+
);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
context.logger.warn(
|
|
208
|
+
{ queueUrl: message.queueUrl, error: describe(error) },
|
|
209
|
+
"Cannot delay the retry of the message"
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return false;
|
|
214
|
+
};
|
|
215
|
+
var failureOptions = (label, values) => {
|
|
216
|
+
const deadLetterQueueUrl = values.deadLetterQueueUrl === void 0 ? void 0 : checkUrl(label, "deadLetterQueueUrl", values.deadLetterQueueUrl);
|
|
217
|
+
checkFunction(label, "retryDelay", values.retryDelay);
|
|
218
|
+
return {
|
|
219
|
+
client: clientOf(label, values),
|
|
220
|
+
deadLetterQueueUrl,
|
|
221
|
+
retryDelay: values.retryDelay,
|
|
222
|
+
logger: values.logger === void 0 ? silentLogger : checkLogger(label, values.logger)
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
var FAILURE_OPTIONS = [
|
|
226
|
+
"client",
|
|
227
|
+
"clientConfig",
|
|
228
|
+
"deadLetterQueueUrl",
|
|
229
|
+
"retryDelay",
|
|
230
|
+
"logger"
|
|
231
|
+
];
|
|
232
|
+
var createSqsHandler = (worker, options = {}) => {
|
|
233
|
+
const label = "createSqsHandler()";
|
|
234
|
+
if (typeof worker?.handle !== "function") {
|
|
235
|
+
throw new TypeError(
|
|
236
|
+
`${label}: expected a worker created by createWorker().`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
const values = checkOptions(label, options, [...FAILURE_OPTIONS, "queueUrl"]);
|
|
240
|
+
const queueUrl = values.queueUrl === void 0 ? void 0 : checkUrl(label, "queueUrl", values.queueUrl);
|
|
241
|
+
const context = failureOptions(label, values);
|
|
242
|
+
const handleRecord = async (record) => {
|
|
243
|
+
const attempt = Number(record.attributes?.ApproximateReceiveCount) || 1;
|
|
244
|
+
const result = await worker.handle(record.body, { attempt });
|
|
245
|
+
return settle(
|
|
246
|
+
result,
|
|
247
|
+
{
|
|
248
|
+
body: record.body,
|
|
249
|
+
receiptHandle: record.receiptHandle,
|
|
250
|
+
attempt,
|
|
251
|
+
queueUrl: queueUrl ?? (record.eventSourceARN ? queueUrlOf(record.eventSourceARN) : void 0)
|
|
252
|
+
},
|
|
253
|
+
context
|
|
254
|
+
);
|
|
255
|
+
};
|
|
256
|
+
return async (event) => {
|
|
257
|
+
const records = event?.Records ?? [];
|
|
258
|
+
const failures = [];
|
|
259
|
+
const fifo = records.some(
|
|
260
|
+
({ eventSourceARN }) => eventSourceARN?.endsWith(".fifo")
|
|
261
|
+
);
|
|
262
|
+
if (fifo) {
|
|
263
|
+
for (const [index, record] of records.entries()) {
|
|
264
|
+
if (!await handleRecord(record)) {
|
|
265
|
+
for (const rest of records.slice(index)) {
|
|
266
|
+
failures.push({ itemIdentifier: rest.messageId });
|
|
267
|
+
}
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
} else {
|
|
272
|
+
const deleted = await Promise.all(records.map(handleRecord));
|
|
273
|
+
records.forEach(({ messageId }, index) => {
|
|
274
|
+
if (!deleted[index]) failures.push({ itemIdentifier: messageId });
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
return { batchItemFailures: failures };
|
|
278
|
+
};
|
|
279
|
+
};
|
|
280
|
+
var sleep = (ms, signal) => new Promise((resolve) => {
|
|
281
|
+
const timer = setTimeout(done, ms);
|
|
282
|
+
function done() {
|
|
283
|
+
clearTimeout(timer);
|
|
284
|
+
signal.removeEventListener("abort", done);
|
|
285
|
+
resolve();
|
|
286
|
+
}
|
|
287
|
+
signal.addEventListener("abort", done, { once: true });
|
|
288
|
+
});
|
|
289
|
+
var createSqsSource = (options) => {
|
|
290
|
+
const label = "createSqsSource()";
|
|
291
|
+
const values = checkOptions(label, options, [
|
|
292
|
+
...FAILURE_OPTIONS,
|
|
293
|
+
"queueUrl",
|
|
294
|
+
"waitTimeSeconds",
|
|
295
|
+
"batchSize",
|
|
296
|
+
"visibilityTimeout",
|
|
297
|
+
"checkRedrivePolicy"
|
|
298
|
+
]);
|
|
299
|
+
const queueUrl = checkUrl(label, "queueUrl", values.queueUrl);
|
|
300
|
+
const waitTimeSeconds = checkInteger(
|
|
301
|
+
label,
|
|
302
|
+
"waitTimeSeconds",
|
|
303
|
+
values.waitTimeSeconds,
|
|
304
|
+
0,
|
|
305
|
+
MAX_WAIT_SECONDS
|
|
306
|
+
) ?? MAX_WAIT_SECONDS;
|
|
307
|
+
const batchSize = checkInteger(label, "batchSize", values.batchSize, 1, MAX_BATCH_ENTRIES) ?? MAX_BATCH_ENTRIES;
|
|
308
|
+
const visibilityTimeout = checkInteger(
|
|
309
|
+
label,
|
|
310
|
+
"visibilityTimeout",
|
|
311
|
+
values.visibilityTimeout,
|
|
312
|
+
1,
|
|
313
|
+
MAX_VISIBILITY_SECONDS
|
|
314
|
+
);
|
|
315
|
+
if (values.checkRedrivePolicy !== void 0 && typeof values.checkRedrivePolicy !== "boolean") {
|
|
316
|
+
throw new TypeError(`${label}: checkRedrivePolicy must be a boolean.`);
|
|
317
|
+
}
|
|
318
|
+
const failure = failureOptions(label, values);
|
|
319
|
+
const { client } = failure;
|
|
320
|
+
return {
|
|
321
|
+
kind: "aws-sqs",
|
|
322
|
+
consume: async (worker, { signal, logger }) => {
|
|
323
|
+
const context = {
|
|
324
|
+
...failure,
|
|
325
|
+
logger: values.logger ? failure.logger : logger
|
|
326
|
+
};
|
|
327
|
+
if (values.checkRedrivePolicy !== false) {
|
|
328
|
+
try {
|
|
329
|
+
const result = await client().send(
|
|
330
|
+
new GetQueueAttributesCommand({
|
|
331
|
+
QueueUrl: queueUrl,
|
|
332
|
+
AttributeNames: ["RedrivePolicy"]
|
|
333
|
+
})
|
|
334
|
+
);
|
|
335
|
+
if (!result.Attributes?.RedrivePolicy) {
|
|
336
|
+
context.logger.warn(
|
|
337
|
+
{ queueUrl },
|
|
338
|
+
"The SQS queue has no dead-letter queue: failed messages are retried until they expire"
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
} catch (error) {
|
|
342
|
+
context.logger.warn(
|
|
343
|
+
{ queueUrl, error: describe(error) },
|
|
344
|
+
"Cannot read the redrive policy of the SQS queue"
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const handleMessage = async (message) => {
|
|
349
|
+
const attempt = Number(message.Attributes?.ApproximateReceiveCount) || 1;
|
|
350
|
+
const receiptHandle = message.ReceiptHandle;
|
|
351
|
+
const heartbeat = visibilityTimeout === void 0 ? void 0 : setInterval(
|
|
352
|
+
() => {
|
|
353
|
+
client().send(
|
|
354
|
+
new ChangeMessageVisibilityCommand({
|
|
355
|
+
QueueUrl: queueUrl,
|
|
356
|
+
ReceiptHandle: receiptHandle,
|
|
357
|
+
VisibilityTimeout: visibilityTimeout
|
|
358
|
+
})
|
|
359
|
+
).catch((error) => {
|
|
360
|
+
context.logger.warn(
|
|
361
|
+
{ queueUrl, error: describe(error) },
|
|
362
|
+
"Cannot extend the visibility of the message"
|
|
363
|
+
);
|
|
364
|
+
});
|
|
365
|
+
},
|
|
366
|
+
visibilityTimeout * 1e3 / 2
|
|
367
|
+
);
|
|
368
|
+
try {
|
|
369
|
+
const result = await worker.handle(message.Body ?? "", { attempt });
|
|
370
|
+
return await settle(
|
|
371
|
+
result,
|
|
372
|
+
{ body: message.Body ?? "", receiptHandle, attempt, queueUrl },
|
|
373
|
+
context
|
|
374
|
+
);
|
|
375
|
+
} finally {
|
|
376
|
+
clearInterval(heartbeat);
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
let errorDelay = 0;
|
|
380
|
+
while (!signal.aborted) {
|
|
381
|
+
let messages;
|
|
382
|
+
try {
|
|
383
|
+
const result = await client().send(
|
|
384
|
+
new ReceiveMessageCommand({
|
|
385
|
+
QueueUrl: queueUrl,
|
|
386
|
+
MaxNumberOfMessages: batchSize,
|
|
387
|
+
WaitTimeSeconds: waitTimeSeconds,
|
|
388
|
+
MessageSystemAttributeNames: ["ApproximateReceiveCount"],
|
|
389
|
+
...visibilityTimeout === void 0 ? {} : { VisibilityTimeout: visibilityTimeout }
|
|
390
|
+
}),
|
|
391
|
+
{ abortSignal: signal }
|
|
392
|
+
);
|
|
393
|
+
messages = result.Messages ?? [];
|
|
394
|
+
errorDelay = 0;
|
|
395
|
+
} catch (error) {
|
|
396
|
+
if (signal.aborted) break;
|
|
397
|
+
errorDelay = Math.min(3e4, Math.max(1e3, errorDelay * 2));
|
|
398
|
+
context.logger.error(
|
|
399
|
+
{ queueUrl, error: describe(error), retryInMs: errorDelay },
|
|
400
|
+
"Cannot receive SQS messages"
|
|
401
|
+
);
|
|
402
|
+
await sleep(errorDelay, signal);
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
const deleted = await Promise.all(messages.map(handleMessage));
|
|
406
|
+
const entries = messages.filter((_, index) => deleted[index]).map(({ ReceiptHandle }, index) => ({
|
|
407
|
+
Id: String(index),
|
|
408
|
+
ReceiptHandle
|
|
409
|
+
}));
|
|
410
|
+
if (entries.length === 0) continue;
|
|
411
|
+
try {
|
|
412
|
+
const result = await client().send(
|
|
413
|
+
new DeleteMessageBatchCommand({
|
|
414
|
+
QueueUrl: queueUrl,
|
|
415
|
+
Entries: entries
|
|
416
|
+
})
|
|
417
|
+
);
|
|
418
|
+
if (result.Failed?.length) {
|
|
419
|
+
context.logger.error(
|
|
420
|
+
{ queueUrl, failed: result.Failed.length },
|
|
421
|
+
"Cannot delete handled SQS messages: they will be delivered again"
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
} catch (error) {
|
|
425
|
+
context.logger.error(
|
|
426
|
+
{ queueUrl, error: describe(error) },
|
|
427
|
+
"Cannot delete handled SQS messages: they will be delivered again"
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
};
|
|
434
|
+
export {
|
|
435
|
+
createSqsHandler,
|
|
436
|
+
createSqsSource,
|
|
437
|
+
createSqsTransport,
|
|
438
|
+
queueUrlOf
|
|
439
|
+
};
|
|
440
|
+
//# sourceMappingURL=aws-sqs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/transports/aws-sqs.ts"],"sourcesContent":["import {\n ChangeMessageVisibilityCommand,\n DeleteMessageBatchCommand,\n GetQueueAttributesCommand,\n ReceiveMessageCommand,\n SendMessageBatchCommand,\n SendMessageCommand,\n SQSClient,\n type MessageAttributeValue,\n type SQSClientConfig,\n type SendMessageBatchRequestEntry,\n} from '@aws-sdk/client-sqs'\nimport type { Envelope } from '../envelope.js'\nimport { PartialSendError } from '../errors.js'\nimport { checkLogger, silentLogger, type EventsLogger } from '../logger.js'\nimport type { Source, Transport } from '../transport.js'\nimport type { HandleResult, Worker } from '../worker.js'\n\n/** An SQS client, or any object with the same `send()` method. */\nexport interface SqsClient {\n send(\n command: object,\n options?: { abortSignal?: AbortSignal }\n ): Promise<unknown>\n}\n\n/** Client options shared by the transport, the handler and the source. */\nexport interface SqsClientOptions {\n /** Existing client. Defaults to a client created with `clientConfig`. */\n client?: SqsClient | undefined\n /** Configuration of the created client: `region`, `credentials`… */\n clientConfig?: SQSClientConfig | undefined\n}\n\n/** Options of `createSqsTransport()`. */\nexport interface SqsTransportOptions extends SqsClientOptions {\n /** URL of the queue. A `.fifo` URL makes a FIFO queue. */\n queueUrl: string\n /**\n * FIFO queues: message group of a message, ordered within its group.\n * Defaults to the listener: messages of a listener are handled in order.\n */\n messageGroupId?: ((envelope: Envelope) => string) | undefined\n}\n\n/** How failed messages are handled, by the Lambda handler and the source. */\nexport interface SqsFailureOptions extends SqsClientOptions {\n /**\n * Dead-letter queue of the queue: messages that can never be handled\n * (invalid payload, unknown listener) are copied there at once, instead\n * of failing until the `maxReceiveCount` of the queue.\n */\n deadLetterQueueUrl?: string | undefined\n /**\n * Delay before a failed message is delivered again, in seconds, given its\n * attempt number. Defaults to the visibility timeout of the queue.\n */\n retryDelay?: ((attempt: number) => number) | undefined\n logger?: EventsLogger | undefined\n}\n\n/** Options of `createSqsHandler()`. */\nexport interface SqsHandlerOptions extends SqsFailureOptions {\n /**\n * URL of the queue, for `retryDelay`. Defaults to the URL derived from\n * the ARN of the records.\n */\n queueUrl?: string | undefined\n}\n\n/** Options of `createSqsSource()`. */\nexport interface SqsSourceOptions extends SqsFailureOptions {\n queueUrl: string\n /** Long polling, in seconds (0 to 20). Defaults to 20. */\n waitTimeSeconds?: number | undefined\n /** Messages per request (1 to 10). Defaults to 10. */\n batchSize?: number | undefined\n /**\n * Visibility timeout of the received messages, in seconds. When set, the\n * source extends it while a message is being handled, so that a long\n * listener is not delivered twice. Defaults to the one of the queue.\n */\n visibilityTimeout?: number | undefined\n /**\n * Warns at startup when the queue has no dead-letter queue (redrive\n * policy). Defaults to true; needs `sqs:GetQueueAttributes`.\n */\n checkRedrivePolicy?: boolean | undefined\n}\n\n/** Record of an SQS event received by a Lambda function. */\nexport interface SqsRecord {\n readonly messageId: string\n readonly receiptHandle: string\n readonly body: string\n readonly attributes: { readonly ApproximateReceiveCount?: string }\n readonly eventSourceARN?: string\n}\n\n/** SQS event received by a Lambda function. */\nexport interface SqsEvent {\n readonly Records: readonly SqsRecord[]\n}\n\n/** Response of the Lambda handler (`ReportBatchItemFailures`). */\nexport interface SqsBatchResponse {\n batchItemFailures: { itemIdentifier: string }[]\n}\n\n// Limits of SQS.\nconst MAX_BATCH_ENTRIES = 10\nconst MAX_MESSAGE_BYTES = 1_048_576\nconst MAX_DELAY_SECONDS = 900\nconst MAX_VISIBILITY_SECONDS = 43_200\nconst MAX_WAIT_SECONDS = 20\n\nconst encoder = new TextEncoder()\nconst bytesOf = (text: string) => encoder.encode(text).byteLength\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst checkOptions = (\n label: string,\n options: unknown,\n allowed: readonly string[]\n): Record<string, unknown> => {\n if (!isRecord(options)) {\n throw new TypeError(`${label}: expected an object of options.`)\n }\n for (const key of Object.keys(options)) {\n if (!allowed.includes(key)) {\n throw new TypeError(`${label}: unknown option \"${key}\".`)\n }\n }\n return options\n}\n\nconst checkUrl = (label: string, key: string, value: unknown) => {\n if (typeof value !== 'string' || !/^https?:\\/\\/[^/]/.test(value)) {\n throw new TypeError(`${label}: ${key} must be the URL of an SQS queue.`)\n }\n return value\n}\n\nconst checkInteger = (\n label: string,\n key: string,\n value: unknown,\n min: number,\n max: number\n) => {\n if (\n value !== undefined &&\n (!Number.isInteger(value) ||\n (value as number) < min ||\n (value as number) > max)\n ) {\n throw new TypeError(\n `${label}: ${key} must be an integer from ${min} to ${max}.`\n )\n }\n return value as number | undefined\n}\n\nconst checkFunction = (label: string, key: string, value: unknown) => {\n if (value !== undefined && typeof value !== 'function') {\n throw new TypeError(`${label}: ${key} must be a function.`)\n }\n}\n\n/** Client of the options, created on first use. */\nconst clientOf = (label: string, values: Record<string, unknown>) => {\n const { client, clientConfig } = values\n if (client !== undefined && clientConfig !== undefined) {\n throw new TypeError(`${label}: set client or clientConfig, not both.`)\n }\n if (\n client !== undefined &&\n typeof (client as SqsClient | null)?.send !== 'function'\n ) {\n throw new TypeError(`${label}: client must have a send() method.`)\n }\n let created: SqsClient | undefined\n return () =>\n (client as SqsClient | undefined) ??\n (created ??= new SQSClient((clientConfig as SQSClientConfig) ?? {}))\n}\n\nconst attributesOf = (\n envelope: Envelope\n): Record<string, MessageAttributeValue> => ({\n event: { DataType: 'String', StringValue: envelope.event },\n listener: { DataType: 'String', StringValue: envelope.listener },\n})\n\n/** Size of a message for SQS: body and attributes. */\nconst sizeOf = (\n body: string,\n attributes: Record<string, MessageAttributeValue>\n) =>\n Object.entries(attributes).reduce(\n (total, [name, { DataType = '', StringValue = '' }]) =>\n total + bytesOf(name) + bytesOf(DataType) + bytesOf(StringValue),\n bytesOf(body)\n )\n\nconst describe = (error: unknown) =>\n error instanceof Error ? error.message : String(error)\n\n/**\n * Creates a transport that sends messages to an Amazon SQS queue, through\n * the AWS SDK (`@aws-sdk/client-sqs`, peer dependency). Messages are sent\n * in batches of 10; each one carries `event` and `listener` attributes.\n *\n * ```ts\n * const transport = createSqsTransport({ queueUrl: process.env.EVENTS_QUEUE_URL! })\n * ```\n */\nexport const createSqsTransport = (options: SqsTransportOptions): Transport => {\n const label = 'createSqsTransport()'\n const values = checkOptions(label, options, [\n 'queueUrl',\n 'client',\n 'clientConfig',\n 'messageGroupId',\n ])\n const queueUrl = checkUrl(label, 'queueUrl', values.queueUrl)\n checkFunction(label, 'messageGroupId', values.messageGroupId)\n const client = clientOf(label, values)\n const fifo = queueUrl.endsWith('.fifo')\n const groupOf =\n (values.messageGroupId as SqsTransportOptions['messageGroupId']) ??\n ((envelope: Envelope) => envelope.listener)\n\n return {\n kind: 'aws-sqs',\n send: async (envelopes, { delay }) => {\n if (delay !== undefined && delay > MAX_DELAY_SECONDS) {\n throw new RangeError(\n `SQS accepts delays of at most ${MAX_DELAY_SECONDS} seconds, got ${delay}.`\n )\n }\n if (fifo && delay !== undefined && delay > 0) {\n throw new RangeError(\n 'SQS FIFO queues do not accept per-message delays.'\n )\n }\n\n const failed: { id: string; error: unknown }[] = []\n const batches: {\n entries: SendMessageBatchRequestEntry[]\n size: number\n }[] = []\n for (const envelope of envelopes) {\n const body = JSON.stringify(envelope)\n const attributes = attributesOf(envelope)\n const size = sizeOf(body, attributes)\n if (size > MAX_MESSAGE_BYTES) {\n failed.push({\n id: envelope.id,\n error: new RangeError(\n `SQS accepts messages of at most ${MAX_MESSAGE_BYTES} bytes (attributes included), got ${size}.`\n ),\n })\n continue\n }\n const entry: SendMessageBatchRequestEntry = {\n Id: envelope.id,\n MessageBody: body,\n MessageAttributes: attributes,\n ...(fifo\n ? {\n MessageGroupId: groupOf(envelope),\n // Duplicates of a message are dropped within 5 minutes.\n MessageDeduplicationId: envelope.id,\n }\n : delay === undefined\n ? {}\n : { DelaySeconds: delay }),\n }\n const last = batches.at(-1)\n if (\n last &&\n last.entries.length < MAX_BATCH_ENTRIES &&\n last.size + size <= MAX_MESSAGE_BYTES\n ) {\n last.entries.push(entry)\n last.size += size\n } else {\n batches.push({ entries: [entry], size })\n }\n }\n\n for (const { entries } of batches) {\n try {\n const result = (await client().send(\n new SendMessageBatchCommand({\n QueueUrl: queueUrl,\n Entries: entries,\n })\n )) as { Failed?: { Id?: string; Code?: string; Message?: string }[] }\n for (const { Id, Code, Message } of result.Failed ?? []) {\n failed.push({\n id: Id as string,\n error: new Error(`${Code}${Message ? `: ${Message}` : ''}`),\n })\n }\n } catch (error) {\n for (const { Id } of entries) failed.push({ id: Id as string, error })\n }\n }\n if (failed.length > 0) throw new PartialSendError(failed)\n },\n }\n}\n\n/** URL of the queue of an ARN (`arn:aws:sqs:region:account:name`). */\nexport const queueUrlOf = (arn: string) => {\n const [, partition, , region, account, name] = arn.split(':')\n if (!partition || !region || !account || !name) return undefined\n const domain = partition === 'aws-cn' ? 'amazonaws.com.cn' : 'amazonaws.com'\n return `https://sqs.${region}.${domain}/${account}/${name}`\n}\n\n/** Applies the result of a message: whether to delete it from its queue. */\nconst settle = async (\n result: HandleResult,\n message: {\n body: string\n receiptHandle: string\n attempt: number\n queueUrl: string | undefined\n },\n context: {\n client: () => SqsClient\n deadLetterQueueUrl: string | undefined\n retryDelay: ((attempt: number) => number) | undefined\n logger: EventsLogger\n }\n): Promise<boolean> => {\n if (result.status === 'success') return true\n\n if (result.status === 'dead' && context.deadLetterQueueUrl) {\n try {\n await context.client().send(\n new SendMessageCommand({\n QueueUrl: context.deadLetterQueueUrl,\n MessageBody: message.body,\n MessageAttributes: {\n reason: { DataType: 'String', StringValue: result.reason },\n error: {\n DataType: 'String',\n StringValue: describe(result.error).slice(0, 1024),\n },\n },\n })\n )\n return true\n } catch (error) {\n context.logger.error(\n {\n deadLetterQueueUrl: context.deadLetterQueueUrl,\n error: describe(error),\n },\n 'Cannot copy the message to the dead-letter queue'\n )\n return false\n }\n }\n\n if (result.status === 'retry' && context.retryDelay && message.queueUrl) {\n const delay = Math.min(\n MAX_VISIBILITY_SECONDS,\n Math.max(0, Math.round(context.retryDelay(message.attempt)))\n )\n try {\n await context.client().send(\n new ChangeMessageVisibilityCommand({\n QueueUrl: message.queueUrl,\n ReceiptHandle: message.receiptHandle,\n VisibilityTimeout: delay,\n })\n )\n } catch (error) {\n context.logger.warn(\n { queueUrl: message.queueUrl, error: describe(error) },\n 'Cannot delay the retry of the message'\n )\n }\n }\n return false\n}\n\nconst failureOptions = (label: string, values: Record<string, unknown>) => {\n const deadLetterQueueUrl =\n values.deadLetterQueueUrl === undefined\n ? undefined\n : checkUrl(label, 'deadLetterQueueUrl', values.deadLetterQueueUrl)\n checkFunction(label, 'retryDelay', values.retryDelay)\n return {\n client: clientOf(label, values),\n deadLetterQueueUrl,\n retryDelay: values.retryDelay as ((attempt: number) => number) | undefined,\n logger:\n values.logger === undefined\n ? silentLogger\n : checkLogger(label, values.logger),\n }\n}\n\nconst FAILURE_OPTIONS = [\n 'client',\n 'clientConfig',\n 'deadLetterQueueUrl',\n 'retryDelay',\n 'logger',\n]\n\n/**\n * Creates the handler of a Lambda function triggered by the SQS queue.\n * Enable `ReportBatchItemFailures` on the event source mapping: only the\n * failed messages are retried.\n *\n * ```ts\n * export const handler = createSqsHandler(createWorker({ listeners }))\n * ```\n */\nexport const createSqsHandler = (\n worker: Worker,\n options: SqsHandlerOptions = {}\n) => {\n const label = 'createSqsHandler()'\n if (typeof worker?.handle !== 'function') {\n throw new TypeError(\n `${label}: expected a worker created by createWorker().`\n )\n }\n const values = checkOptions(label, options, [...FAILURE_OPTIONS, 'queueUrl'])\n const queueUrl =\n values.queueUrl === undefined\n ? undefined\n : checkUrl(label, 'queueUrl', values.queueUrl)\n const context = failureOptions(label, values)\n\n const handleRecord = async (record: SqsRecord) => {\n const attempt = Number(record.attributes?.ApproximateReceiveCount) || 1\n const result = await worker.handle(record.body, { attempt })\n return settle(\n result,\n {\n body: record.body,\n receiptHandle: record.receiptHandle,\n attempt,\n queueUrl:\n queueUrl ??\n (record.eventSourceARN\n ? queueUrlOf(record.eventSourceARN)\n : undefined),\n },\n context\n )\n }\n\n return async (event: SqsEvent): Promise<SqsBatchResponse> => {\n const records = event?.Records ?? []\n const failures: { itemIdentifier: string }[] = []\n const fifo = records.some(({ eventSourceARN }) =>\n eventSourceARN?.endsWith('.fifo')\n )\n if (fifo) {\n // Order matters: stop at the first failure, retry the rest.\n for (const [index, record] of records.entries()) {\n if (!(await handleRecord(record))) {\n for (const rest of records.slice(index)) {\n failures.push({ itemIdentifier: rest.messageId })\n }\n break\n }\n }\n } else {\n const deleted = await Promise.all(records.map(handleRecord))\n records.forEach(({ messageId }, index) => {\n if (!deleted[index]) failures.push({ itemIdentifier: messageId })\n })\n }\n return { batchItemFailures: failures }\n }\n}\n\nconst sleep = (ms: number, signal: AbortSignal) =>\n new Promise<void>((resolve) => {\n const timer = setTimeout(done, ms)\n function done() {\n clearTimeout(timer)\n signal.removeEventListener('abort', done)\n resolve()\n }\n signal.addEventListener('abort', done, { once: true })\n })\n\ninterface ReceivedMessage {\n Body?: string\n ReceiptHandle?: string\n Attributes?: Record<string, string>\n}\n\n/**\n * Creates a source that reads the SQS queue by long polling, for\n * `startWorker()` and `dotpress-events work`: a container, a VM, or your\n * machine reading a development queue.\n *\n * ```ts\n * export default {\n * worker: createWorker({ listeners }),\n * source: createSqsSource({ queueUrl: process.env.EVENTS_QUEUE_URL! }),\n * }\n * ```\n */\nexport const createSqsSource = (options: SqsSourceOptions): Source => {\n const label = 'createSqsSource()'\n const values = checkOptions(label, options, [\n ...FAILURE_OPTIONS,\n 'queueUrl',\n 'waitTimeSeconds',\n 'batchSize',\n 'visibilityTimeout',\n 'checkRedrivePolicy',\n ])\n const queueUrl = checkUrl(label, 'queueUrl', values.queueUrl)\n const waitTimeSeconds =\n checkInteger(\n label,\n 'waitTimeSeconds',\n values.waitTimeSeconds,\n 0,\n MAX_WAIT_SECONDS\n ) ?? MAX_WAIT_SECONDS\n const batchSize =\n checkInteger(label, 'batchSize', values.batchSize, 1, MAX_BATCH_ENTRIES) ??\n MAX_BATCH_ENTRIES\n const visibilityTimeout = checkInteger(\n label,\n 'visibilityTimeout',\n values.visibilityTimeout,\n 1,\n MAX_VISIBILITY_SECONDS\n )\n if (\n values.checkRedrivePolicy !== undefined &&\n typeof values.checkRedrivePolicy !== 'boolean'\n ) {\n throw new TypeError(`${label}: checkRedrivePolicy must be a boolean.`)\n }\n const failure = failureOptions(label, values)\n const { client } = failure\n\n return {\n kind: 'aws-sqs',\n consume: async (worker, { signal, logger }) => {\n const context = {\n ...failure,\n logger: values.logger ? failure.logger : logger,\n }\n\n if (values.checkRedrivePolicy !== false) {\n try {\n const result = (await client().send(\n new GetQueueAttributesCommand({\n QueueUrl: queueUrl,\n AttributeNames: ['RedrivePolicy'],\n })\n )) as { Attributes?: Record<string, string> }\n if (!result.Attributes?.RedrivePolicy) {\n context.logger.warn(\n { queueUrl },\n 'The SQS queue has no dead-letter queue: failed messages are retried until they expire'\n )\n }\n } catch (error) {\n context.logger.warn(\n { queueUrl, error: describe(error) },\n 'Cannot read the redrive policy of the SQS queue'\n )\n }\n }\n\n const handleMessage = async (message: ReceivedMessage) => {\n const attempt = Number(message.Attributes?.ApproximateReceiveCount) || 1\n const receiptHandle = message.ReceiptHandle as string\n // Keeps the message invisible while its listener runs.\n const heartbeat =\n visibilityTimeout === undefined\n ? undefined\n : setInterval(\n () => {\n client()\n .send(\n new ChangeMessageVisibilityCommand({\n QueueUrl: queueUrl,\n ReceiptHandle: receiptHandle,\n VisibilityTimeout: visibilityTimeout,\n })\n )\n .catch((error: unknown) => {\n context.logger.warn(\n { queueUrl, error: describe(error) },\n 'Cannot extend the visibility of the message'\n )\n })\n },\n (visibilityTimeout * 1000) / 2\n )\n try {\n const result = await worker.handle(message.Body ?? '', { attempt })\n return await settle(\n result,\n { body: message.Body ?? '', receiptHandle, attempt, queueUrl },\n context\n )\n } finally {\n clearInterval(heartbeat)\n }\n }\n\n let errorDelay = 0\n while (!signal.aborted) {\n let messages: ReceivedMessage[]\n try {\n const result = (await client().send(\n new ReceiveMessageCommand({\n QueueUrl: queueUrl,\n MaxNumberOfMessages: batchSize,\n WaitTimeSeconds: waitTimeSeconds,\n MessageSystemAttributeNames: ['ApproximateReceiveCount'],\n ...(visibilityTimeout === undefined\n ? {}\n : { VisibilityTimeout: visibilityTimeout }),\n }),\n { abortSignal: signal }\n )) as { Messages?: ReceivedMessage[] }\n messages = result.Messages ?? []\n errorDelay = 0\n } catch (error) {\n if (signal.aborted) break\n errorDelay = Math.min(30_000, Math.max(1000, errorDelay * 2))\n context.logger.error(\n { queueUrl, error: describe(error), retryInMs: errorDelay },\n 'Cannot receive SQS messages'\n )\n await sleep(errorDelay, signal)\n continue\n }\n\n const deleted = await Promise.all(messages.map(handleMessage))\n const entries = messages\n .filter((_, index) => deleted[index])\n .map(({ ReceiptHandle }, index) => ({\n Id: String(index),\n ReceiptHandle: ReceiptHandle as string,\n }))\n if (entries.length === 0) continue\n try {\n const result = (await client().send(\n new DeleteMessageBatchCommand({\n QueueUrl: queueUrl,\n Entries: entries,\n })\n )) as { Failed?: { Id?: string; Message?: string }[] }\n if (result.Failed?.length) {\n context.logger.error(\n { queueUrl, failed: result.Failed.length },\n 'Cannot delete handled SQS messages: they will be delivered again'\n )\n }\n } catch (error) {\n context.logger.error(\n { queueUrl, error: describe(error) },\n 'Cannot delete handled SQS messages: they will be delivered again'\n )\n }\n }\n },\n }\n}\n"],"mappings":"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","names":[]}
|