@babelqueue/sqs 1.0.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 +74 -0
- package/dist/index.cjs +174 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +140 -0
- package/dist/index.d.ts +140 -0
- package/dist/index.js +147 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# @babelqueue/sqs
|
|
2
|
+
|
|
3
|
+
Amazon SQS adapter for [BabelQueue](https://babelqueue.com) — "Polyglot Queues,
|
|
4
|
+
Simplified."
|
|
5
|
+
|
|
6
|
+
A canonical-envelope **publisher** and a URN-routed **consumer** over Amazon SQS, so
|
|
7
|
+
an SQS-based Node service speaks the same wire contract as the PHP/Laravel, Python,
|
|
8
|
+
Go, Java and .NET SDKs. Implements [§3 of the broker-bindings contract](https://babelqueue.com):
|
|
9
|
+
the canonical envelope is the message body, projected onto native SQS
|
|
10
|
+
`MessageAttributes`.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm i @babelqueue/sqs @aws-sdk/client-sqs
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`@aws-sdk/client-sqs` is an (optional) peer — you provide the SQS client.
|
|
19
|
+
|
|
20
|
+
## Use
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { SQS } from "@aws-sdk/client-sqs";
|
|
24
|
+
import { SqsPublisher, SqsConsumer } from "@babelqueue/sqs";
|
|
25
|
+
|
|
26
|
+
const sqs = new SQS({ region: "eu-central-1" });
|
|
27
|
+
const url = "https://sqs.eu-central-1.amazonaws.com/123456789012/orders";
|
|
28
|
+
|
|
29
|
+
// produce
|
|
30
|
+
await new SqsPublisher(sqs, url).publish("urn:babel:orders:created", { order_id: 1042 });
|
|
31
|
+
|
|
32
|
+
// consume
|
|
33
|
+
const consumer = new SqsConsumer(sqs, url, {
|
|
34
|
+
"urn:babel:orders:created": async (env) => {
|
|
35
|
+
// env.data, env.trace_id, env.attempts ...
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
await consumer.run(); // long-polls until the AbortSignal you pass aborts
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
FIFO queues: `new SqsPublisher(sqs, url, { fifo: true })` (the URL must end in `.fifo`).
|
|
42
|
+
For LocalStack/ElasticMQ, point the `SQS` client's `endpoint` there.
|
|
43
|
+
|
|
44
|
+
## Contract mapping (§3)
|
|
45
|
+
|
|
46
|
+
| Envelope | SQS |
|
|
47
|
+
| :--- | :--- |
|
|
48
|
+
| body | `MessageBody` (byte-identical across SDKs) |
|
|
49
|
+
| `job` (URN) | `MessageAttributes.bq-job` |
|
|
50
|
+
| `trace_id` | `MessageAttributes.bq-trace-id` |
|
|
51
|
+
| `meta.id` | `MessageAttributes.bq-message-id` |
|
|
52
|
+
| `meta.schema_version` | `MessageAttributes.bq-schema-version` (Number) |
|
|
53
|
+
| `meta.lang` | `MessageAttributes.bq-source-lang` |
|
|
54
|
+
| `meta.created_at` | `MessageAttributes.bq-created-at` (Number, ms) |
|
|
55
|
+
| `attempts` | reconciled to `ApproximateReceiveCount − 1` on receive |
|
|
56
|
+
| reserve / ack | visibility timeout → `DeleteMessage` |
|
|
57
|
+
|
|
58
|
+
Retry is **SQS-native**: a throwing handler leaves the message undeleted, so SQS
|
|
59
|
+
redelivers it after the visibility timeout (at-least-once). The loop never stops on a
|
|
60
|
+
bad message — use `onError` / `onUnknownUrn` to observe. The envelope is unchanged
|
|
61
|
+
(`schema_version` stays `1`); SQS is purely additive.
|
|
62
|
+
|
|
63
|
+
## Test
|
|
64
|
+
|
|
65
|
+
The unit tests inject a fake SQS client (`SqsApi`), so they run without
|
|
66
|
+
`@aws-sdk/client-sqs` and without a broker:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm test
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
SqsConsumer: () => SqsConsumer,
|
|
24
|
+
SqsPublisher: () => SqsPublisher,
|
|
25
|
+
toMessageAttributes: () => toMessageAttributes
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
var import_core = require("@babelqueue/core");
|
|
29
|
+
var str = (value) => ({
|
|
30
|
+
DataType: "String",
|
|
31
|
+
StringValue: String(value)
|
|
32
|
+
});
|
|
33
|
+
var num = (value) => ({
|
|
34
|
+
DataType: "Number",
|
|
35
|
+
StringValue: String(value)
|
|
36
|
+
});
|
|
37
|
+
function toMessageAttributes(envelope) {
|
|
38
|
+
const attrs = {};
|
|
39
|
+
if (envelope.job) attrs["bq-job"] = str(envelope.job);
|
|
40
|
+
if (envelope.trace_id) attrs["bq-trace-id"] = str(envelope.trace_id);
|
|
41
|
+
if (envelope.meta.id) attrs["bq-message-id"] = str(envelope.meta.id);
|
|
42
|
+
if (envelope.meta.schema_version != null) {
|
|
43
|
+
attrs["bq-schema-version"] = num(envelope.meta.schema_version);
|
|
44
|
+
}
|
|
45
|
+
if (envelope.meta.lang) attrs["bq-source-lang"] = str(envelope.meta.lang);
|
|
46
|
+
if (envelope.meta.created_at != null) {
|
|
47
|
+
attrs["bq-created-at"] = num(envelope.meta.created_at);
|
|
48
|
+
}
|
|
49
|
+
return attrs;
|
|
50
|
+
}
|
|
51
|
+
function queueNameFromUrl(queueUrl) {
|
|
52
|
+
const segments = queueUrl.split("/").filter(Boolean);
|
|
53
|
+
return segments[segments.length - 1] ?? "default";
|
|
54
|
+
}
|
|
55
|
+
var SqsPublisher = class {
|
|
56
|
+
constructor(client, queueUrl, options = {}) {
|
|
57
|
+
this.client = client;
|
|
58
|
+
this.queueUrl = queueUrl;
|
|
59
|
+
this.options = options;
|
|
60
|
+
}
|
|
61
|
+
client;
|
|
62
|
+
queueUrl;
|
|
63
|
+
options;
|
|
64
|
+
/**
|
|
65
|
+
* Build the canonical envelope for `(urn, data)`, send it as the message body with
|
|
66
|
+
* the projected `MessageAttributes`, and return the message id (`meta.id`).
|
|
67
|
+
*/
|
|
68
|
+
async publish(urn, data, options = {}) {
|
|
69
|
+
const envelope = import_core.EnvelopeCodec.make(urn, data, {
|
|
70
|
+
queue: queueNameFromUrl(this.queueUrl),
|
|
71
|
+
traceId: options.traceId
|
|
72
|
+
});
|
|
73
|
+
const input = {
|
|
74
|
+
QueueUrl: this.queueUrl,
|
|
75
|
+
MessageBody: import_core.EnvelopeCodec.encode(envelope),
|
|
76
|
+
MessageAttributes: toMessageAttributes(envelope)
|
|
77
|
+
};
|
|
78
|
+
if (this.options.fifo) {
|
|
79
|
+
input.MessageGroupId = this.options.messageGroupId ?? queueNameFromUrl(this.queueUrl);
|
|
80
|
+
if (!this.options.contentDedup) {
|
|
81
|
+
input.MessageDeduplicationId = envelope.meta.id;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
await this.client.sendMessage(input);
|
|
85
|
+
return envelope.meta.id;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
var SqsConsumer = class {
|
|
89
|
+
constructor(client, queueUrl, handlers, options = {}) {
|
|
90
|
+
this.client = client;
|
|
91
|
+
this.queueUrl = queueUrl;
|
|
92
|
+
this.handlers = handlers;
|
|
93
|
+
this.options = options;
|
|
94
|
+
}
|
|
95
|
+
client;
|
|
96
|
+
queueUrl;
|
|
97
|
+
handlers;
|
|
98
|
+
options;
|
|
99
|
+
/** Receive one batch, route each message, delete the ones handled. Returns the batch size. */
|
|
100
|
+
async poll() {
|
|
101
|
+
const input = {
|
|
102
|
+
QueueUrl: this.queueUrl,
|
|
103
|
+
MaxNumberOfMessages: this.options.maxMessages ?? 10,
|
|
104
|
+
WaitTimeSeconds: this.options.waitTimeSeconds ?? 20,
|
|
105
|
+
MessageAttributeNames: ["All"],
|
|
106
|
+
AttributeNames: ["ApproximateReceiveCount"]
|
|
107
|
+
};
|
|
108
|
+
if (this.options.visibilityTimeout != null) {
|
|
109
|
+
input.VisibilityTimeout = this.options.visibilityTimeout;
|
|
110
|
+
}
|
|
111
|
+
const result = await this.client.receiveMessage(input);
|
|
112
|
+
const messages = result.Messages ?? [];
|
|
113
|
+
for (const message of messages) {
|
|
114
|
+
await this.handle(message);
|
|
115
|
+
}
|
|
116
|
+
return messages.length;
|
|
117
|
+
}
|
|
118
|
+
/** Poll until `signal` aborts (each poll long-polls, so this does not busy-loop). */
|
|
119
|
+
async run(signal) {
|
|
120
|
+
while (signal?.aborted !== true) {
|
|
121
|
+
await this.poll();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async handle(message) {
|
|
125
|
+
const envelope = import_core.EnvelopeCodec.decode(message.Body ?? "");
|
|
126
|
+
const receiveCount = message.Attributes?.["ApproximateReceiveCount"];
|
|
127
|
+
if (receiveCount !== void 0) {
|
|
128
|
+
const native = Number.parseInt(receiveCount, 10) - 1;
|
|
129
|
+
const current = typeof envelope.attempts === "number" ? envelope.attempts : 0;
|
|
130
|
+
if (Number.isFinite(native) && native > current) {
|
|
131
|
+
envelope.attempts = native;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!import_core.EnvelopeCodec.accepts(envelope)) {
|
|
135
|
+
this.options.onError?.(
|
|
136
|
+
new import_core.BabelQueueError("Rejected a non-conformant BabelQueue envelope from SQS."),
|
|
137
|
+
envelope,
|
|
138
|
+
message
|
|
139
|
+
);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const urn = import_core.EnvelopeCodec.urn(envelope);
|
|
143
|
+
const handler = this.handlers[urn];
|
|
144
|
+
if (!handler) {
|
|
145
|
+
if (this.options.onUnknownUrn) {
|
|
146
|
+
await this.options.onUnknownUrn(envelope, message);
|
|
147
|
+
await this.delete(message);
|
|
148
|
+
} else {
|
|
149
|
+
this.options.onError?.(new import_core.UnknownUrnError(urn), envelope, message);
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
await handler(envelope, message);
|
|
155
|
+
await this.delete(message);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
this.options.onError?.(error, envelope, message);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async delete(message) {
|
|
161
|
+
if (!message.ReceiptHandle) return;
|
|
162
|
+
await this.client.deleteMessage({
|
|
163
|
+
QueueUrl: this.queueUrl,
|
|
164
|
+
ReceiptHandle: message.ReceiptHandle
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
169
|
+
0 && (module.exports = {
|
|
170
|
+
SqsConsumer,
|
|
171
|
+
SqsPublisher,
|
|
172
|
+
toMessageAttributes
|
|
173
|
+
});
|
|
174
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Amazon SQS adapter for BabelQueue.\n *\n * A canonical-envelope **publisher** and a URN-routed **consumer** over Amazon SQS,\n * so an SQS-based Node service speaks the same contract (envelope shape, URN\n * identity, trace propagation) as the PHP/Laravel, Python, Go, Java and .NET SDKs.\n *\n * import { SQS } from \"@aws-sdk/client-sqs\";\n * import { SqsPublisher, SqsConsumer } from \"@babelqueue/sqs\";\n *\n * const sqs = new SQS({ region: \"eu-central-1\" });\n * const url = \"https://sqs.eu-central-1.amazonaws.com/123456789012/orders\";\n *\n * await new SqsPublisher(sqs, url).publish(\"urn:babel:orders:created\", { order_id: 1042 });\n *\n * const consumer = new SqsConsumer(sqs, url, {\n * \"urn:babel:orders:created\": async (env) => { ... },\n * });\n * await consumer.run();\n *\n * This implements §3 of the broker-bindings contract: the canonical envelope is the\n * message body, projected onto native SQS `MessageAttributes`. The envelope is\n * unchanged (`schema_version` stays 1); SQS is purely additive. Retry is SQS-native\n * (a failed handler leaves the message for visibility-timeout redelivery); the\n * authoritative attempt count is `ApproximateReceiveCount`, surfaced to handlers as\n * `attempts = count − 1`.\n */\n\nimport { BabelQueueError, EnvelopeCodec, UnknownUrnError } from \"@babelqueue/core\";\nimport type { Envelope, IncomingEnvelope } from \"@babelqueue/core\";\n\n// --- Minimal SQS shapes (a structural subset of @aws-sdk/client-sqs) -----------\n\nexport interface SqsMessageAttributeValue {\n DataType: string;\n StringValue?: string;\n}\n\nexport interface SqsMessage {\n Body?: string;\n ReceiptHandle?: string;\n MessageAttributes?: Record<string, SqsMessageAttributeValue>;\n Attributes?: Record<string, string>;\n}\n\nexport interface SendMessageInput {\n QueueUrl: string;\n MessageBody: string;\n MessageAttributes?: Record<string, SqsMessageAttributeValue>;\n MessageGroupId?: string;\n MessageDeduplicationId?: string;\n}\n\nexport interface ReceiveMessageInput {\n QueueUrl: string;\n MaxNumberOfMessages?: number;\n WaitTimeSeconds?: number;\n VisibilityTimeout?: number;\n MessageAttributeNames?: string[];\n AttributeNames?: string[];\n}\n\n/**\n * The subset of the AWS SQS client this adapter calls. The aggregated `SQS` class\n * from `@aws-sdk/client-sqs` satisfies it structurally; a fake satisfies it in tests.\n */\nexport interface SqsApi {\n sendMessage(input: SendMessageInput): Promise<{ MessageId?: string } | unknown>;\n receiveMessage(input: ReceiveMessageInput): Promise<{ Messages?: SqsMessage[] }>;\n deleteMessage(input: { QueueUrl: string; ReceiptHandle: string }): Promise<unknown>;\n}\n\n// --- Attribute projection (contract §3.2) --------------------------------------\n\nconst str = (value: unknown): SqsMessageAttributeValue => ({\n DataType: \"String\",\n StringValue: String(value),\n});\nconst num = (value: unknown): SqsMessageAttributeValue => ({\n DataType: \"Number\",\n StringValue: String(value),\n});\n\n/**\n * Project the envelope's contract fields onto native SQS `MessageAttributes` — a\n * redundant, routable view of the body (the body stays authoritative).\n */\nexport function toMessageAttributes(envelope: Envelope): Record<string, SqsMessageAttributeValue> {\n const attrs: Record<string, SqsMessageAttributeValue> = {};\n if (envelope.job) attrs[\"bq-job\"] = str(envelope.job);\n if (envelope.trace_id) attrs[\"bq-trace-id\"] = str(envelope.trace_id);\n if (envelope.meta.id) attrs[\"bq-message-id\"] = str(envelope.meta.id);\n if (envelope.meta.schema_version != null) {\n attrs[\"bq-schema-version\"] = num(envelope.meta.schema_version);\n }\n if (envelope.meta.lang) attrs[\"bq-source-lang\"] = str(envelope.meta.lang);\n if (envelope.meta.created_at != null) {\n attrs[\"bq-created-at\"] = num(envelope.meta.created_at);\n }\n return attrs;\n}\n\nfunction queueNameFromUrl(queueUrl: string): string {\n const segments = queueUrl.split(\"/\").filter(Boolean);\n return segments[segments.length - 1] ?? \"default\";\n}\n\n// --- Publisher -----------------------------------------------------------------\n\n/** Options for {@link SqsPublisher}. */\nexport interface SqsPublisherOptions {\n /** Treat the queue as FIFO: set `MessageGroupId` and (unless content dedup) `MessageDeduplicationId`. */\n fifo?: boolean;\n /** FIFO ordering group (default: the queue name from the URL). */\n messageGroupId?: string;\n /** Use the queue's content-based dedup instead of `meta.id` as the dedup id. */\n contentDedup?: boolean;\n}\n\n/** Options for {@link SqsPublisher.publish}. */\nexport interface PublishOptions {\n /** Reuse an existing trace id (trace continuation). */\n traceId?: string;\n}\n\n/** Sends canonical-envelope messages to one SQS queue with the §3 attribute projection. */\nexport class SqsPublisher {\n constructor(\n private readonly client: SqsApi,\n private readonly queueUrl: string,\n private readonly options: SqsPublisherOptions = {},\n ) {}\n\n /**\n * Build the canonical envelope for `(urn, data)`, send it as the message body with\n * the projected `MessageAttributes`, and return the message id (`meta.id`).\n */\n async publish(\n urn: string,\n data: Record<string, unknown>,\n options: PublishOptions = {},\n ): Promise<string> {\n const envelope = EnvelopeCodec.make(urn, data, {\n queue: queueNameFromUrl(this.queueUrl),\n traceId: options.traceId,\n });\n const input: SendMessageInput = {\n QueueUrl: this.queueUrl,\n MessageBody: EnvelopeCodec.encode(envelope),\n MessageAttributes: toMessageAttributes(envelope),\n };\n if (this.options.fifo) {\n input.MessageGroupId = this.options.messageGroupId ?? queueNameFromUrl(this.queueUrl);\n if (!this.options.contentDedup) {\n input.MessageDeduplicationId = envelope.meta.id;\n }\n }\n await this.client.sendMessage(input);\n return envelope.meta.id;\n }\n}\n\n// --- Consumer ------------------------------------------------------------------\n\n/** A URN handler. Receives the validated envelope and the raw SQS message. */\nexport type BabelHandler = (envelope: Envelope, message: SqsMessage) => unknown | Promise<unknown>;\n\n/** A map of URN → handler. */\nexport type BabelHandlers = Record<string, BabelHandler>;\n\n/** Options for {@link SqsConsumer}. */\nexport interface SqsConsumerOptions {\n /** Called instead of erroring when a message's URN has no handler (then the message is deleted). */\n onUnknownUrn?: (envelope: IncomingEnvelope, message: SqsMessage) => unknown | Promise<unknown>;\n /** Called for a non-conformant envelope, an unmapped URN (no `onUnknownUrn`), or a throwing handler. The loop never stops. */\n onError?: (error: unknown, envelope: IncomingEnvelope, message: SqsMessage) => void;\n /** Long-poll wait seconds (default 20). */\n waitTimeSeconds?: number;\n /** Reservation window applied on receive (seconds). */\n visibilityTimeout?: number;\n /** Max messages per receive (default 10). */\n maxMessages?: number;\n}\n\n/**\n * Polls an SQS queue, decodes + validates each message, routes it to the handler\n * registered for its URN, and deletes it on success. A throwing handler leaves the\n * message undeleted — SQS redelivers it after the visibility timeout (at-least-once);\n * `attempts` is reconciled to `ApproximateReceiveCount − 1` for the handler.\n */\nexport class SqsConsumer {\n constructor(\n private readonly client: SqsApi,\n private readonly queueUrl: string,\n private readonly handlers: BabelHandlers,\n private readonly options: SqsConsumerOptions = {},\n ) {}\n\n /** Receive one batch, route each message, delete the ones handled. Returns the batch size. */\n async poll(): Promise<number> {\n const input: ReceiveMessageInput = {\n QueueUrl: this.queueUrl,\n MaxNumberOfMessages: this.options.maxMessages ?? 10,\n WaitTimeSeconds: this.options.waitTimeSeconds ?? 20,\n MessageAttributeNames: [\"All\"],\n AttributeNames: [\"ApproximateReceiveCount\"],\n };\n if (this.options.visibilityTimeout != null) {\n input.VisibilityTimeout = this.options.visibilityTimeout;\n }\n const result = await this.client.receiveMessage(input);\n const messages = result.Messages ?? [];\n for (const message of messages) {\n await this.handle(message);\n }\n return messages.length;\n }\n\n /** Poll until `signal` aborts (each poll long-polls, so this does not busy-loop). */\n async run(signal?: AbortSignal): Promise<void> {\n while (signal?.aborted !== true) {\n await this.poll();\n }\n }\n\n private async handle(message: SqsMessage): Promise<void> {\n const envelope = EnvelopeCodec.decode(message.Body ?? \"\");\n\n const receiveCount = message.Attributes?.[\"ApproximateReceiveCount\"];\n if (receiveCount !== undefined) {\n const native = Number.parseInt(receiveCount, 10) - 1;\n const current = typeof envelope.attempts === \"number\" ? envelope.attempts : 0;\n if (Number.isFinite(native) && native > current) {\n envelope.attempts = native;\n }\n }\n\n if (!EnvelopeCodec.accepts(envelope)) {\n this.options.onError?.(\n new BabelQueueError(\"Rejected a non-conformant BabelQueue envelope from SQS.\"),\n envelope,\n message,\n );\n return;\n }\n\n const urn = EnvelopeCodec.urn(envelope);\n const handler = this.handlers[urn];\n if (!handler) {\n if (this.options.onUnknownUrn) {\n await this.options.onUnknownUrn(envelope, message);\n await this.delete(message);\n } else {\n this.options.onError?.(new UnknownUrnError(urn), envelope, message);\n }\n return;\n }\n\n try {\n await handler(envelope, message);\n await this.delete(message);\n } catch (error) {\n // Leave the message undeleted — SQS redelivers after the visibility timeout.\n this.options.onError?.(error, envelope, message);\n }\n }\n\n private async delete(message: SqsMessage): Promise<void> {\n if (!message.ReceiptHandle) return;\n await this.client.deleteMessage({\n QueueUrl: this.queueUrl,\n ReceiptHandle: message.ReceiptHandle,\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4BA,kBAAgE;AA8ChE,IAAM,MAAM,CAAC,WAA8C;AAAA,EACzD,UAAU;AAAA,EACV,aAAa,OAAO,KAAK;AAC3B;AACA,IAAM,MAAM,CAAC,WAA8C;AAAA,EACzD,UAAU;AAAA,EACV,aAAa,OAAO,KAAK;AAC3B;AAMO,SAAS,oBAAoB,UAA8D;AAChG,QAAM,QAAkD,CAAC;AACzD,MAAI,SAAS,IAAK,OAAM,QAAQ,IAAI,IAAI,SAAS,GAAG;AACpD,MAAI,SAAS,SAAU,OAAM,aAAa,IAAI,IAAI,SAAS,QAAQ;AACnE,MAAI,SAAS,KAAK,GAAI,OAAM,eAAe,IAAI,IAAI,SAAS,KAAK,EAAE;AACnE,MAAI,SAAS,KAAK,kBAAkB,MAAM;AACxC,UAAM,mBAAmB,IAAI,IAAI,SAAS,KAAK,cAAc;AAAA,EAC/D;AACA,MAAI,SAAS,KAAK,KAAM,OAAM,gBAAgB,IAAI,IAAI,SAAS,KAAK,IAAI;AACxE,MAAI,SAAS,KAAK,cAAc,MAAM;AACpC,UAAM,eAAe,IAAI,IAAI,SAAS,KAAK,UAAU;AAAA,EACvD;AACA,SAAO;AACT;AAEA,SAAS,iBAAiB,UAA0B;AAClD,QAAM,WAAW,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO;AACnD,SAAO,SAAS,SAAS,SAAS,CAAC,KAAK;AAC1C;AAqBO,IAAM,eAAN,MAAmB;AAAA,EACxB,YACmB,QACA,UACA,UAA+B,CAAC,GACjD;AAHiB;AACA;AACA;AAAA,EAChB;AAAA,EAHgB;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOnB,MAAM,QACJ,KACA,MACA,UAA0B,CAAC,GACV;AACjB,UAAM,WAAW,0BAAc,KAAK,KAAK,MAAM;AAAA,MAC7C,OAAO,iBAAiB,KAAK,QAAQ;AAAA,MACrC,SAAS,QAAQ;AAAA,IACnB,CAAC;AACD,UAAM,QAA0B;AAAA,MAC9B,UAAU,KAAK;AAAA,MACf,aAAa,0BAAc,OAAO,QAAQ;AAAA,MAC1C,mBAAmB,oBAAoB,QAAQ;AAAA,IACjD;AACA,QAAI,KAAK,QAAQ,MAAM;AACrB,YAAM,iBAAiB,KAAK,QAAQ,kBAAkB,iBAAiB,KAAK,QAAQ;AACpF,UAAI,CAAC,KAAK,QAAQ,cAAc;AAC9B,cAAM,yBAAyB,SAAS,KAAK;AAAA,MAC/C;AAAA,IACF;AACA,UAAM,KAAK,OAAO,YAAY,KAAK;AACnC,WAAO,SAAS,KAAK;AAAA,EACvB;AACF;AA8BO,IAAM,cAAN,MAAkB;AAAA,EACvB,YACmB,QACA,UACA,UACA,UAA8B,CAAC,GAChD;AAJiB;AACA;AACA;AACA;AAAA,EAChB;AAAA,EAJgB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAInB,MAAM,OAAwB;AAC5B,UAAM,QAA6B;AAAA,MACjC,UAAU,KAAK;AAAA,MACf,qBAAqB,KAAK,QAAQ,eAAe;AAAA,MACjD,iBAAiB,KAAK,QAAQ,mBAAmB;AAAA,MACjD,uBAAuB,CAAC,KAAK;AAAA,MAC7B,gBAAgB,CAAC,yBAAyB;AAAA,IAC5C;AACA,QAAI,KAAK,QAAQ,qBAAqB,MAAM;AAC1C,YAAM,oBAAoB,KAAK,QAAQ;AAAA,IACzC;AACA,UAAM,SAAS,MAAM,KAAK,OAAO,eAAe,KAAK;AACrD,UAAM,WAAW,OAAO,YAAY,CAAC;AACrC,eAAW,WAAW,UAAU;AAC9B,YAAM,KAAK,OAAO,OAAO;AAAA,IAC3B;AACA,WAAO,SAAS;AAAA,EAClB;AAAA;AAAA,EAGA,MAAM,IAAI,QAAqC;AAC7C,WAAO,QAAQ,YAAY,MAAM;AAC/B,YAAM,KAAK,KAAK;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,MAAc,OAAO,SAAoC;AACvD,UAAM,WAAW,0BAAc,OAAO,QAAQ,QAAQ,EAAE;AAExD,UAAM,eAAe,QAAQ,aAAa,yBAAyB;AACnE,QAAI,iBAAiB,QAAW;AAC9B,YAAM,SAAS,OAAO,SAAS,cAAc,EAAE,IAAI;AACnD,YAAM,UAAU,OAAO,SAAS,aAAa,WAAW,SAAS,WAAW;AAC5E,UAAI,OAAO,SAAS,MAAM,KAAK,SAAS,SAAS;AAC/C,iBAAS,WAAW;AAAA,MACtB;AAAA,IACF;AAEA,QAAI,CAAC,0BAAc,QAAQ,QAAQ,GAAG;AACpC,WAAK,QAAQ;AAAA,QACX,IAAI,4BAAgB,yDAAyD;AAAA,QAC7E;AAAA,QACA;AAAA,MACF;AACA;AAAA,IACF;AAEA,UAAM,MAAM,0BAAc,IAAI,QAAQ;AACtC,UAAM,UAAU,KAAK,SAAS,GAAG;AACjC,QAAI,CAAC,SAAS;AACZ,UAAI,KAAK,QAAQ,cAAc;AAC7B,cAAM,KAAK,QAAQ,aAAa,UAAU,OAAO;AACjD,cAAM,KAAK,OAAO,OAAO;AAAA,MAC3B,OAAO;AACL,aAAK,QAAQ,UAAU,IAAI,4BAAgB,GAAG,GAAG,UAAU,OAAO;AAAA,MACpE;AACA;AAAA,IACF;AAEA,QAAI;AACF,YAAM,QAAQ,UAAU,OAAO;AAC/B,YAAM,KAAK,OAAO,OAAO;AAAA,IAC3B,SAAS,OAAO;AAEd,WAAK,QAAQ,UAAU,OAAO,UAAU,OAAO;AAAA,IACjD;AAAA,EACF;AAAA,EAEA,MAAc,OAAO,SAAoC;AACvD,QAAI,CAAC,QAAQ,cAAe;AAC5B,UAAM,KAAK,OAAO,cAAc;AAAA,MAC9B,UAAU,KAAK;AAAA,MACf,eAAe,QAAQ;AAAA,IACzB,CAAC;AAAA,EACH;AACF;","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Envelope, IncomingEnvelope } from '@babelqueue/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Amazon SQS adapter for BabelQueue.
|
|
5
|
+
*
|
|
6
|
+
* A canonical-envelope **publisher** and a URN-routed **consumer** over Amazon SQS,
|
|
7
|
+
* so an SQS-based Node service speaks the same contract (envelope shape, URN
|
|
8
|
+
* identity, trace propagation) as the PHP/Laravel, Python, Go, Java and .NET SDKs.
|
|
9
|
+
*
|
|
10
|
+
* import { SQS } from "@aws-sdk/client-sqs";
|
|
11
|
+
* import { SqsPublisher, SqsConsumer } from "@babelqueue/sqs";
|
|
12
|
+
*
|
|
13
|
+
* const sqs = new SQS({ region: "eu-central-1" });
|
|
14
|
+
* const url = "https://sqs.eu-central-1.amazonaws.com/123456789012/orders";
|
|
15
|
+
*
|
|
16
|
+
* await new SqsPublisher(sqs, url).publish("urn:babel:orders:created", { order_id: 1042 });
|
|
17
|
+
*
|
|
18
|
+
* const consumer = new SqsConsumer(sqs, url, {
|
|
19
|
+
* "urn:babel:orders:created": async (env) => { ... },
|
|
20
|
+
* });
|
|
21
|
+
* await consumer.run();
|
|
22
|
+
*
|
|
23
|
+
* This implements §3 of the broker-bindings contract: the canonical envelope is the
|
|
24
|
+
* message body, projected onto native SQS `MessageAttributes`. The envelope is
|
|
25
|
+
* unchanged (`schema_version` stays 1); SQS is purely additive. Retry is SQS-native
|
|
26
|
+
* (a failed handler leaves the message for visibility-timeout redelivery); the
|
|
27
|
+
* authoritative attempt count is `ApproximateReceiveCount`, surfaced to handlers as
|
|
28
|
+
* `attempts = count − 1`.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
interface SqsMessageAttributeValue {
|
|
32
|
+
DataType: string;
|
|
33
|
+
StringValue?: string;
|
|
34
|
+
}
|
|
35
|
+
interface SqsMessage {
|
|
36
|
+
Body?: string;
|
|
37
|
+
ReceiptHandle?: string;
|
|
38
|
+
MessageAttributes?: Record<string, SqsMessageAttributeValue>;
|
|
39
|
+
Attributes?: Record<string, string>;
|
|
40
|
+
}
|
|
41
|
+
interface SendMessageInput {
|
|
42
|
+
QueueUrl: string;
|
|
43
|
+
MessageBody: string;
|
|
44
|
+
MessageAttributes?: Record<string, SqsMessageAttributeValue>;
|
|
45
|
+
MessageGroupId?: string;
|
|
46
|
+
MessageDeduplicationId?: string;
|
|
47
|
+
}
|
|
48
|
+
interface ReceiveMessageInput {
|
|
49
|
+
QueueUrl: string;
|
|
50
|
+
MaxNumberOfMessages?: number;
|
|
51
|
+
WaitTimeSeconds?: number;
|
|
52
|
+
VisibilityTimeout?: number;
|
|
53
|
+
MessageAttributeNames?: string[];
|
|
54
|
+
AttributeNames?: string[];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The subset of the AWS SQS client this adapter calls. The aggregated `SQS` class
|
|
58
|
+
* from `@aws-sdk/client-sqs` satisfies it structurally; a fake satisfies it in tests.
|
|
59
|
+
*/
|
|
60
|
+
interface SqsApi {
|
|
61
|
+
sendMessage(input: SendMessageInput): Promise<{
|
|
62
|
+
MessageId?: string;
|
|
63
|
+
} | unknown>;
|
|
64
|
+
receiveMessage(input: ReceiveMessageInput): Promise<{
|
|
65
|
+
Messages?: SqsMessage[];
|
|
66
|
+
}>;
|
|
67
|
+
deleteMessage(input: {
|
|
68
|
+
QueueUrl: string;
|
|
69
|
+
ReceiptHandle: string;
|
|
70
|
+
}): Promise<unknown>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Project the envelope's contract fields onto native SQS `MessageAttributes` — a
|
|
74
|
+
* redundant, routable view of the body (the body stays authoritative).
|
|
75
|
+
*/
|
|
76
|
+
declare function toMessageAttributes(envelope: Envelope): Record<string, SqsMessageAttributeValue>;
|
|
77
|
+
/** Options for {@link SqsPublisher}. */
|
|
78
|
+
interface SqsPublisherOptions {
|
|
79
|
+
/** Treat the queue as FIFO: set `MessageGroupId` and (unless content dedup) `MessageDeduplicationId`. */
|
|
80
|
+
fifo?: boolean;
|
|
81
|
+
/** FIFO ordering group (default: the queue name from the URL). */
|
|
82
|
+
messageGroupId?: string;
|
|
83
|
+
/** Use the queue's content-based dedup instead of `meta.id` as the dedup id. */
|
|
84
|
+
contentDedup?: boolean;
|
|
85
|
+
}
|
|
86
|
+
/** Options for {@link SqsPublisher.publish}. */
|
|
87
|
+
interface PublishOptions {
|
|
88
|
+
/** Reuse an existing trace id (trace continuation). */
|
|
89
|
+
traceId?: string;
|
|
90
|
+
}
|
|
91
|
+
/** Sends canonical-envelope messages to one SQS queue with the §3 attribute projection. */
|
|
92
|
+
declare class SqsPublisher {
|
|
93
|
+
private readonly client;
|
|
94
|
+
private readonly queueUrl;
|
|
95
|
+
private readonly options;
|
|
96
|
+
constructor(client: SqsApi, queueUrl: string, options?: SqsPublisherOptions);
|
|
97
|
+
/**
|
|
98
|
+
* Build the canonical envelope for `(urn, data)`, send it as the message body with
|
|
99
|
+
* the projected `MessageAttributes`, and return the message id (`meta.id`).
|
|
100
|
+
*/
|
|
101
|
+
publish(urn: string, data: Record<string, unknown>, options?: PublishOptions): Promise<string>;
|
|
102
|
+
}
|
|
103
|
+
/** A URN handler. Receives the validated envelope and the raw SQS message. */
|
|
104
|
+
type BabelHandler = (envelope: Envelope, message: SqsMessage) => unknown | Promise<unknown>;
|
|
105
|
+
/** A map of URN → handler. */
|
|
106
|
+
type BabelHandlers = Record<string, BabelHandler>;
|
|
107
|
+
/** Options for {@link SqsConsumer}. */
|
|
108
|
+
interface SqsConsumerOptions {
|
|
109
|
+
/** Called instead of erroring when a message's URN has no handler (then the message is deleted). */
|
|
110
|
+
onUnknownUrn?: (envelope: IncomingEnvelope, message: SqsMessage) => unknown | Promise<unknown>;
|
|
111
|
+
/** Called for a non-conformant envelope, an unmapped URN (no `onUnknownUrn`), or a throwing handler. The loop never stops. */
|
|
112
|
+
onError?: (error: unknown, envelope: IncomingEnvelope, message: SqsMessage) => void;
|
|
113
|
+
/** Long-poll wait seconds (default 20). */
|
|
114
|
+
waitTimeSeconds?: number;
|
|
115
|
+
/** Reservation window applied on receive (seconds). */
|
|
116
|
+
visibilityTimeout?: number;
|
|
117
|
+
/** Max messages per receive (default 10). */
|
|
118
|
+
maxMessages?: number;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Polls an SQS queue, decodes + validates each message, routes it to the handler
|
|
122
|
+
* registered for its URN, and deletes it on success. A throwing handler leaves the
|
|
123
|
+
* message undeleted — SQS redelivers it after the visibility timeout (at-least-once);
|
|
124
|
+
* `attempts` is reconciled to `ApproximateReceiveCount − 1` for the handler.
|
|
125
|
+
*/
|
|
126
|
+
declare class SqsConsumer {
|
|
127
|
+
private readonly client;
|
|
128
|
+
private readonly queueUrl;
|
|
129
|
+
private readonly handlers;
|
|
130
|
+
private readonly options;
|
|
131
|
+
constructor(client: SqsApi, queueUrl: string, handlers: BabelHandlers, options?: SqsConsumerOptions);
|
|
132
|
+
/** Receive one batch, route each message, delete the ones handled. Returns the batch size. */
|
|
133
|
+
poll(): Promise<number>;
|
|
134
|
+
/** Poll until `signal` aborts (each poll long-polls, so this does not busy-loop). */
|
|
135
|
+
run(signal?: AbortSignal): Promise<void>;
|
|
136
|
+
private handle;
|
|
137
|
+
private delete;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export { type BabelHandler, type BabelHandlers, type PublishOptions, type ReceiveMessageInput, type SendMessageInput, type SqsApi, SqsConsumer, type SqsConsumerOptions, type SqsMessage, type SqsMessageAttributeValue, SqsPublisher, type SqsPublisherOptions, toMessageAttributes };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Envelope, IncomingEnvelope } from '@babelqueue/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Amazon SQS adapter for BabelQueue.
|
|
5
|
+
*
|
|
6
|
+
* A canonical-envelope **publisher** and a URN-routed **consumer** over Amazon SQS,
|
|
7
|
+
* so an SQS-based Node service speaks the same contract (envelope shape, URN
|
|
8
|
+
* identity, trace propagation) as the PHP/Laravel, Python, Go, Java and .NET SDKs.
|
|
9
|
+
*
|
|
10
|
+
* import { SQS } from "@aws-sdk/client-sqs";
|
|
11
|
+
* import { SqsPublisher, SqsConsumer } from "@babelqueue/sqs";
|
|
12
|
+
*
|
|
13
|
+
* const sqs = new SQS({ region: "eu-central-1" });
|
|
14
|
+
* const url = "https://sqs.eu-central-1.amazonaws.com/123456789012/orders";
|
|
15
|
+
*
|
|
16
|
+
* await new SqsPublisher(sqs, url).publish("urn:babel:orders:created", { order_id: 1042 });
|
|
17
|
+
*
|
|
18
|
+
* const consumer = new SqsConsumer(sqs, url, {
|
|
19
|
+
* "urn:babel:orders:created": async (env) => { ... },
|
|
20
|
+
* });
|
|
21
|
+
* await consumer.run();
|
|
22
|
+
*
|
|
23
|
+
* This implements §3 of the broker-bindings contract: the canonical envelope is the
|
|
24
|
+
* message body, projected onto native SQS `MessageAttributes`. The envelope is
|
|
25
|
+
* unchanged (`schema_version` stays 1); SQS is purely additive. Retry is SQS-native
|
|
26
|
+
* (a failed handler leaves the message for visibility-timeout redelivery); the
|
|
27
|
+
* authoritative attempt count is `ApproximateReceiveCount`, surfaced to handlers as
|
|
28
|
+
* `attempts = count − 1`.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
interface SqsMessageAttributeValue {
|
|
32
|
+
DataType: string;
|
|
33
|
+
StringValue?: string;
|
|
34
|
+
}
|
|
35
|
+
interface SqsMessage {
|
|
36
|
+
Body?: string;
|
|
37
|
+
ReceiptHandle?: string;
|
|
38
|
+
MessageAttributes?: Record<string, SqsMessageAttributeValue>;
|
|
39
|
+
Attributes?: Record<string, string>;
|
|
40
|
+
}
|
|
41
|
+
interface SendMessageInput {
|
|
42
|
+
QueueUrl: string;
|
|
43
|
+
MessageBody: string;
|
|
44
|
+
MessageAttributes?: Record<string, SqsMessageAttributeValue>;
|
|
45
|
+
MessageGroupId?: string;
|
|
46
|
+
MessageDeduplicationId?: string;
|
|
47
|
+
}
|
|
48
|
+
interface ReceiveMessageInput {
|
|
49
|
+
QueueUrl: string;
|
|
50
|
+
MaxNumberOfMessages?: number;
|
|
51
|
+
WaitTimeSeconds?: number;
|
|
52
|
+
VisibilityTimeout?: number;
|
|
53
|
+
MessageAttributeNames?: string[];
|
|
54
|
+
AttributeNames?: string[];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The subset of the AWS SQS client this adapter calls. The aggregated `SQS` class
|
|
58
|
+
* from `@aws-sdk/client-sqs` satisfies it structurally; a fake satisfies it in tests.
|
|
59
|
+
*/
|
|
60
|
+
interface SqsApi {
|
|
61
|
+
sendMessage(input: SendMessageInput): Promise<{
|
|
62
|
+
MessageId?: string;
|
|
63
|
+
} | unknown>;
|
|
64
|
+
receiveMessage(input: ReceiveMessageInput): Promise<{
|
|
65
|
+
Messages?: SqsMessage[];
|
|
66
|
+
}>;
|
|
67
|
+
deleteMessage(input: {
|
|
68
|
+
QueueUrl: string;
|
|
69
|
+
ReceiptHandle: string;
|
|
70
|
+
}): Promise<unknown>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Project the envelope's contract fields onto native SQS `MessageAttributes` — a
|
|
74
|
+
* redundant, routable view of the body (the body stays authoritative).
|
|
75
|
+
*/
|
|
76
|
+
declare function toMessageAttributes(envelope: Envelope): Record<string, SqsMessageAttributeValue>;
|
|
77
|
+
/** Options for {@link SqsPublisher}. */
|
|
78
|
+
interface SqsPublisherOptions {
|
|
79
|
+
/** Treat the queue as FIFO: set `MessageGroupId` and (unless content dedup) `MessageDeduplicationId`. */
|
|
80
|
+
fifo?: boolean;
|
|
81
|
+
/** FIFO ordering group (default: the queue name from the URL). */
|
|
82
|
+
messageGroupId?: string;
|
|
83
|
+
/** Use the queue's content-based dedup instead of `meta.id` as the dedup id. */
|
|
84
|
+
contentDedup?: boolean;
|
|
85
|
+
}
|
|
86
|
+
/** Options for {@link SqsPublisher.publish}. */
|
|
87
|
+
interface PublishOptions {
|
|
88
|
+
/** Reuse an existing trace id (trace continuation). */
|
|
89
|
+
traceId?: string;
|
|
90
|
+
}
|
|
91
|
+
/** Sends canonical-envelope messages to one SQS queue with the §3 attribute projection. */
|
|
92
|
+
declare class SqsPublisher {
|
|
93
|
+
private readonly client;
|
|
94
|
+
private readonly queueUrl;
|
|
95
|
+
private readonly options;
|
|
96
|
+
constructor(client: SqsApi, queueUrl: string, options?: SqsPublisherOptions);
|
|
97
|
+
/**
|
|
98
|
+
* Build the canonical envelope for `(urn, data)`, send it as the message body with
|
|
99
|
+
* the projected `MessageAttributes`, and return the message id (`meta.id`).
|
|
100
|
+
*/
|
|
101
|
+
publish(urn: string, data: Record<string, unknown>, options?: PublishOptions): Promise<string>;
|
|
102
|
+
}
|
|
103
|
+
/** A URN handler. Receives the validated envelope and the raw SQS message. */
|
|
104
|
+
type BabelHandler = (envelope: Envelope, message: SqsMessage) => unknown | Promise<unknown>;
|
|
105
|
+
/** A map of URN → handler. */
|
|
106
|
+
type BabelHandlers = Record<string, BabelHandler>;
|
|
107
|
+
/** Options for {@link SqsConsumer}. */
|
|
108
|
+
interface SqsConsumerOptions {
|
|
109
|
+
/** Called instead of erroring when a message's URN has no handler (then the message is deleted). */
|
|
110
|
+
onUnknownUrn?: (envelope: IncomingEnvelope, message: SqsMessage) => unknown | Promise<unknown>;
|
|
111
|
+
/** Called for a non-conformant envelope, an unmapped URN (no `onUnknownUrn`), or a throwing handler. The loop never stops. */
|
|
112
|
+
onError?: (error: unknown, envelope: IncomingEnvelope, message: SqsMessage) => void;
|
|
113
|
+
/** Long-poll wait seconds (default 20). */
|
|
114
|
+
waitTimeSeconds?: number;
|
|
115
|
+
/** Reservation window applied on receive (seconds). */
|
|
116
|
+
visibilityTimeout?: number;
|
|
117
|
+
/** Max messages per receive (default 10). */
|
|
118
|
+
maxMessages?: number;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Polls an SQS queue, decodes + validates each message, routes it to the handler
|
|
122
|
+
* registered for its URN, and deletes it on success. A throwing handler leaves the
|
|
123
|
+
* message undeleted — SQS redelivers it after the visibility timeout (at-least-once);
|
|
124
|
+
* `attempts` is reconciled to `ApproximateReceiveCount − 1` for the handler.
|
|
125
|
+
*/
|
|
126
|
+
declare class SqsConsumer {
|
|
127
|
+
private readonly client;
|
|
128
|
+
private readonly queueUrl;
|
|
129
|
+
private readonly handlers;
|
|
130
|
+
private readonly options;
|
|
131
|
+
constructor(client: SqsApi, queueUrl: string, handlers: BabelHandlers, options?: SqsConsumerOptions);
|
|
132
|
+
/** Receive one batch, route each message, delete the ones handled. Returns the batch size. */
|
|
133
|
+
poll(): Promise<number>;
|
|
134
|
+
/** Poll until `signal` aborts (each poll long-polls, so this does not busy-loop). */
|
|
135
|
+
run(signal?: AbortSignal): Promise<void>;
|
|
136
|
+
private handle;
|
|
137
|
+
private delete;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export { type BabelHandler, type BabelHandlers, type PublishOptions, type ReceiveMessageInput, type SendMessageInput, type SqsApi, SqsConsumer, type SqsConsumerOptions, type SqsMessage, type SqsMessageAttributeValue, SqsPublisher, type SqsPublisherOptions, toMessageAttributes };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { BabelQueueError, EnvelopeCodec, UnknownUrnError } from "@babelqueue/core";
|
|
3
|
+
var str = (value) => ({
|
|
4
|
+
DataType: "String",
|
|
5
|
+
StringValue: String(value)
|
|
6
|
+
});
|
|
7
|
+
var num = (value) => ({
|
|
8
|
+
DataType: "Number",
|
|
9
|
+
StringValue: String(value)
|
|
10
|
+
});
|
|
11
|
+
function toMessageAttributes(envelope) {
|
|
12
|
+
const attrs = {};
|
|
13
|
+
if (envelope.job) attrs["bq-job"] = str(envelope.job);
|
|
14
|
+
if (envelope.trace_id) attrs["bq-trace-id"] = str(envelope.trace_id);
|
|
15
|
+
if (envelope.meta.id) attrs["bq-message-id"] = str(envelope.meta.id);
|
|
16
|
+
if (envelope.meta.schema_version != null) {
|
|
17
|
+
attrs["bq-schema-version"] = num(envelope.meta.schema_version);
|
|
18
|
+
}
|
|
19
|
+
if (envelope.meta.lang) attrs["bq-source-lang"] = str(envelope.meta.lang);
|
|
20
|
+
if (envelope.meta.created_at != null) {
|
|
21
|
+
attrs["bq-created-at"] = num(envelope.meta.created_at);
|
|
22
|
+
}
|
|
23
|
+
return attrs;
|
|
24
|
+
}
|
|
25
|
+
function queueNameFromUrl(queueUrl) {
|
|
26
|
+
const segments = queueUrl.split("/").filter(Boolean);
|
|
27
|
+
return segments[segments.length - 1] ?? "default";
|
|
28
|
+
}
|
|
29
|
+
var SqsPublisher = class {
|
|
30
|
+
constructor(client, queueUrl, options = {}) {
|
|
31
|
+
this.client = client;
|
|
32
|
+
this.queueUrl = queueUrl;
|
|
33
|
+
this.options = options;
|
|
34
|
+
}
|
|
35
|
+
client;
|
|
36
|
+
queueUrl;
|
|
37
|
+
options;
|
|
38
|
+
/**
|
|
39
|
+
* Build the canonical envelope for `(urn, data)`, send it as the message body with
|
|
40
|
+
* the projected `MessageAttributes`, and return the message id (`meta.id`).
|
|
41
|
+
*/
|
|
42
|
+
async publish(urn, data, options = {}) {
|
|
43
|
+
const envelope = EnvelopeCodec.make(urn, data, {
|
|
44
|
+
queue: queueNameFromUrl(this.queueUrl),
|
|
45
|
+
traceId: options.traceId
|
|
46
|
+
});
|
|
47
|
+
const input = {
|
|
48
|
+
QueueUrl: this.queueUrl,
|
|
49
|
+
MessageBody: EnvelopeCodec.encode(envelope),
|
|
50
|
+
MessageAttributes: toMessageAttributes(envelope)
|
|
51
|
+
};
|
|
52
|
+
if (this.options.fifo) {
|
|
53
|
+
input.MessageGroupId = this.options.messageGroupId ?? queueNameFromUrl(this.queueUrl);
|
|
54
|
+
if (!this.options.contentDedup) {
|
|
55
|
+
input.MessageDeduplicationId = envelope.meta.id;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
await this.client.sendMessage(input);
|
|
59
|
+
return envelope.meta.id;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
var SqsConsumer = class {
|
|
63
|
+
constructor(client, queueUrl, handlers, options = {}) {
|
|
64
|
+
this.client = client;
|
|
65
|
+
this.queueUrl = queueUrl;
|
|
66
|
+
this.handlers = handlers;
|
|
67
|
+
this.options = options;
|
|
68
|
+
}
|
|
69
|
+
client;
|
|
70
|
+
queueUrl;
|
|
71
|
+
handlers;
|
|
72
|
+
options;
|
|
73
|
+
/** Receive one batch, route each message, delete the ones handled. Returns the batch size. */
|
|
74
|
+
async poll() {
|
|
75
|
+
const input = {
|
|
76
|
+
QueueUrl: this.queueUrl,
|
|
77
|
+
MaxNumberOfMessages: this.options.maxMessages ?? 10,
|
|
78
|
+
WaitTimeSeconds: this.options.waitTimeSeconds ?? 20,
|
|
79
|
+
MessageAttributeNames: ["All"],
|
|
80
|
+
AttributeNames: ["ApproximateReceiveCount"]
|
|
81
|
+
};
|
|
82
|
+
if (this.options.visibilityTimeout != null) {
|
|
83
|
+
input.VisibilityTimeout = this.options.visibilityTimeout;
|
|
84
|
+
}
|
|
85
|
+
const result = await this.client.receiveMessage(input);
|
|
86
|
+
const messages = result.Messages ?? [];
|
|
87
|
+
for (const message of messages) {
|
|
88
|
+
await this.handle(message);
|
|
89
|
+
}
|
|
90
|
+
return messages.length;
|
|
91
|
+
}
|
|
92
|
+
/** Poll until `signal` aborts (each poll long-polls, so this does not busy-loop). */
|
|
93
|
+
async run(signal) {
|
|
94
|
+
while (signal?.aborted !== true) {
|
|
95
|
+
await this.poll();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async handle(message) {
|
|
99
|
+
const envelope = EnvelopeCodec.decode(message.Body ?? "");
|
|
100
|
+
const receiveCount = message.Attributes?.["ApproximateReceiveCount"];
|
|
101
|
+
if (receiveCount !== void 0) {
|
|
102
|
+
const native = Number.parseInt(receiveCount, 10) - 1;
|
|
103
|
+
const current = typeof envelope.attempts === "number" ? envelope.attempts : 0;
|
|
104
|
+
if (Number.isFinite(native) && native > current) {
|
|
105
|
+
envelope.attempts = native;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (!EnvelopeCodec.accepts(envelope)) {
|
|
109
|
+
this.options.onError?.(
|
|
110
|
+
new BabelQueueError("Rejected a non-conformant BabelQueue envelope from SQS."),
|
|
111
|
+
envelope,
|
|
112
|
+
message
|
|
113
|
+
);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const urn = EnvelopeCodec.urn(envelope);
|
|
117
|
+
const handler = this.handlers[urn];
|
|
118
|
+
if (!handler) {
|
|
119
|
+
if (this.options.onUnknownUrn) {
|
|
120
|
+
await this.options.onUnknownUrn(envelope, message);
|
|
121
|
+
await this.delete(message);
|
|
122
|
+
} else {
|
|
123
|
+
this.options.onError?.(new UnknownUrnError(urn), envelope, message);
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
await handler(envelope, message);
|
|
129
|
+
await this.delete(message);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
this.options.onError?.(error, envelope, message);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async delete(message) {
|
|
135
|
+
if (!message.ReceiptHandle) return;
|
|
136
|
+
await this.client.deleteMessage({
|
|
137
|
+
QueueUrl: this.queueUrl,
|
|
138
|
+
ReceiptHandle: message.ReceiptHandle
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
export {
|
|
143
|
+
SqsConsumer,
|
|
144
|
+
SqsPublisher,
|
|
145
|
+
toMessageAttributes
|
|
146
|
+
};
|
|
147
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Amazon SQS adapter for BabelQueue.\n *\n * A canonical-envelope **publisher** and a URN-routed **consumer** over Amazon SQS,\n * so an SQS-based Node service speaks the same contract (envelope shape, URN\n * identity, trace propagation) as the PHP/Laravel, Python, Go, Java and .NET SDKs.\n *\n * import { SQS } from \"@aws-sdk/client-sqs\";\n * import { SqsPublisher, SqsConsumer } from \"@babelqueue/sqs\";\n *\n * const sqs = new SQS({ region: \"eu-central-1\" });\n * const url = \"https://sqs.eu-central-1.amazonaws.com/123456789012/orders\";\n *\n * await new SqsPublisher(sqs, url).publish(\"urn:babel:orders:created\", { order_id: 1042 });\n *\n * const consumer = new SqsConsumer(sqs, url, {\n * \"urn:babel:orders:created\": async (env) => { ... },\n * });\n * await consumer.run();\n *\n * This implements §3 of the broker-bindings contract: the canonical envelope is the\n * message body, projected onto native SQS `MessageAttributes`. The envelope is\n * unchanged (`schema_version` stays 1); SQS is purely additive. Retry is SQS-native\n * (a failed handler leaves the message for visibility-timeout redelivery); the\n * authoritative attempt count is `ApproximateReceiveCount`, surfaced to handlers as\n * `attempts = count − 1`.\n */\n\nimport { BabelQueueError, EnvelopeCodec, UnknownUrnError } from \"@babelqueue/core\";\nimport type { Envelope, IncomingEnvelope } from \"@babelqueue/core\";\n\n// --- Minimal SQS shapes (a structural subset of @aws-sdk/client-sqs) -----------\n\nexport interface SqsMessageAttributeValue {\n DataType: string;\n StringValue?: string;\n}\n\nexport interface SqsMessage {\n Body?: string;\n ReceiptHandle?: string;\n MessageAttributes?: Record<string, SqsMessageAttributeValue>;\n Attributes?: Record<string, string>;\n}\n\nexport interface SendMessageInput {\n QueueUrl: string;\n MessageBody: string;\n MessageAttributes?: Record<string, SqsMessageAttributeValue>;\n MessageGroupId?: string;\n MessageDeduplicationId?: string;\n}\n\nexport interface ReceiveMessageInput {\n QueueUrl: string;\n MaxNumberOfMessages?: number;\n WaitTimeSeconds?: number;\n VisibilityTimeout?: number;\n MessageAttributeNames?: string[];\n AttributeNames?: string[];\n}\n\n/**\n * The subset of the AWS SQS client this adapter calls. The aggregated `SQS` class\n * from `@aws-sdk/client-sqs` satisfies it structurally; a fake satisfies it in tests.\n */\nexport interface SqsApi {\n sendMessage(input: SendMessageInput): Promise<{ MessageId?: string } | unknown>;\n receiveMessage(input: ReceiveMessageInput): Promise<{ Messages?: SqsMessage[] }>;\n deleteMessage(input: { QueueUrl: string; ReceiptHandle: string }): Promise<unknown>;\n}\n\n// --- Attribute projection (contract §3.2) --------------------------------------\n\nconst str = (value: unknown): SqsMessageAttributeValue => ({\n DataType: \"String\",\n StringValue: String(value),\n});\nconst num = (value: unknown): SqsMessageAttributeValue => ({\n DataType: \"Number\",\n StringValue: String(value),\n});\n\n/**\n * Project the envelope's contract fields onto native SQS `MessageAttributes` — a\n * redundant, routable view of the body (the body stays authoritative).\n */\nexport function toMessageAttributes(envelope: Envelope): Record<string, SqsMessageAttributeValue> {\n const attrs: Record<string, SqsMessageAttributeValue> = {};\n if (envelope.job) attrs[\"bq-job\"] = str(envelope.job);\n if (envelope.trace_id) attrs[\"bq-trace-id\"] = str(envelope.trace_id);\n if (envelope.meta.id) attrs[\"bq-message-id\"] = str(envelope.meta.id);\n if (envelope.meta.schema_version != null) {\n attrs[\"bq-schema-version\"] = num(envelope.meta.schema_version);\n }\n if (envelope.meta.lang) attrs[\"bq-source-lang\"] = str(envelope.meta.lang);\n if (envelope.meta.created_at != null) {\n attrs[\"bq-created-at\"] = num(envelope.meta.created_at);\n }\n return attrs;\n}\n\nfunction queueNameFromUrl(queueUrl: string): string {\n const segments = queueUrl.split(\"/\").filter(Boolean);\n return segments[segments.length - 1] ?? \"default\";\n}\n\n// --- Publisher -----------------------------------------------------------------\n\n/** Options for {@link SqsPublisher}. */\nexport interface SqsPublisherOptions {\n /** Treat the queue as FIFO: set `MessageGroupId` and (unless content dedup) `MessageDeduplicationId`. */\n fifo?: boolean;\n /** FIFO ordering group (default: the queue name from the URL). */\n messageGroupId?: string;\n /** Use the queue's content-based dedup instead of `meta.id` as the dedup id. */\n contentDedup?: boolean;\n}\n\n/** Options for {@link SqsPublisher.publish}. */\nexport interface PublishOptions {\n /** Reuse an existing trace id (trace continuation). */\n traceId?: string;\n}\n\n/** Sends canonical-envelope messages to one SQS queue with the §3 attribute projection. */\nexport class SqsPublisher {\n constructor(\n private readonly client: SqsApi,\n private readonly queueUrl: string,\n private readonly options: SqsPublisherOptions = {},\n ) {}\n\n /**\n * Build the canonical envelope for `(urn, data)`, send it as the message body with\n * the projected `MessageAttributes`, and return the message id (`meta.id`).\n */\n async publish(\n urn: string,\n data: Record<string, unknown>,\n options: PublishOptions = {},\n ): Promise<string> {\n const envelope = EnvelopeCodec.make(urn, data, {\n queue: queueNameFromUrl(this.queueUrl),\n traceId: options.traceId,\n });\n const input: SendMessageInput = {\n QueueUrl: this.queueUrl,\n MessageBody: EnvelopeCodec.encode(envelope),\n MessageAttributes: toMessageAttributes(envelope),\n };\n if (this.options.fifo) {\n input.MessageGroupId = this.options.messageGroupId ?? queueNameFromUrl(this.queueUrl);\n if (!this.options.contentDedup) {\n input.MessageDeduplicationId = envelope.meta.id;\n }\n }\n await this.client.sendMessage(input);\n return envelope.meta.id;\n }\n}\n\n// --- Consumer ------------------------------------------------------------------\n\n/** A URN handler. Receives the validated envelope and the raw SQS message. */\nexport type BabelHandler = (envelope: Envelope, message: SqsMessage) => unknown | Promise<unknown>;\n\n/** A map of URN → handler. */\nexport type BabelHandlers = Record<string, BabelHandler>;\n\n/** Options for {@link SqsConsumer}. */\nexport interface SqsConsumerOptions {\n /** Called instead of erroring when a message's URN has no handler (then the message is deleted). */\n onUnknownUrn?: (envelope: IncomingEnvelope, message: SqsMessage) => unknown | Promise<unknown>;\n /** Called for a non-conformant envelope, an unmapped URN (no `onUnknownUrn`), or a throwing handler. The loop never stops. */\n onError?: (error: unknown, envelope: IncomingEnvelope, message: SqsMessage) => void;\n /** Long-poll wait seconds (default 20). */\n waitTimeSeconds?: number;\n /** Reservation window applied on receive (seconds). */\n visibilityTimeout?: number;\n /** Max messages per receive (default 10). */\n maxMessages?: number;\n}\n\n/**\n * Polls an SQS queue, decodes + validates each message, routes it to the handler\n * registered for its URN, and deletes it on success. A throwing handler leaves the\n * message undeleted — SQS redelivers it after the visibility timeout (at-least-once);\n * `attempts` is reconciled to `ApproximateReceiveCount − 1` for the handler.\n */\nexport class SqsConsumer {\n constructor(\n private readonly client: SqsApi,\n private readonly queueUrl: string,\n private readonly handlers: BabelHandlers,\n private readonly options: SqsConsumerOptions = {},\n ) {}\n\n /** Receive one batch, route each message, delete the ones handled. Returns the batch size. */\n async poll(): Promise<number> {\n const input: ReceiveMessageInput = {\n QueueUrl: this.queueUrl,\n MaxNumberOfMessages: this.options.maxMessages ?? 10,\n WaitTimeSeconds: this.options.waitTimeSeconds ?? 20,\n MessageAttributeNames: [\"All\"],\n AttributeNames: [\"ApproximateReceiveCount\"],\n };\n if (this.options.visibilityTimeout != null) {\n input.VisibilityTimeout = this.options.visibilityTimeout;\n }\n const result = await this.client.receiveMessage(input);\n const messages = result.Messages ?? [];\n for (const message of messages) {\n await this.handle(message);\n }\n return messages.length;\n }\n\n /** Poll until `signal` aborts (each poll long-polls, so this does not busy-loop). */\n async run(signal?: AbortSignal): Promise<void> {\n while (signal?.aborted !== true) {\n await this.poll();\n }\n }\n\n private async handle(message: SqsMessage): Promise<void> {\n const envelope = EnvelopeCodec.decode(message.Body ?? \"\");\n\n const receiveCount = message.Attributes?.[\"ApproximateReceiveCount\"];\n if (receiveCount !== undefined) {\n const native = Number.parseInt(receiveCount, 10) - 1;\n const current = typeof envelope.attempts === \"number\" ? envelope.attempts : 0;\n if (Number.isFinite(native) && native > current) {\n envelope.attempts = native;\n }\n }\n\n if (!EnvelopeCodec.accepts(envelope)) {\n this.options.onError?.(\n new BabelQueueError(\"Rejected a non-conformant BabelQueue envelope from SQS.\"),\n envelope,\n message,\n );\n return;\n }\n\n const urn = EnvelopeCodec.urn(envelope);\n const handler = this.handlers[urn];\n if (!handler) {\n if (this.options.onUnknownUrn) {\n await this.options.onUnknownUrn(envelope, message);\n await this.delete(message);\n } else {\n this.options.onError?.(new UnknownUrnError(urn), envelope, message);\n }\n return;\n }\n\n try {\n await handler(envelope, message);\n await this.delete(message);\n } catch (error) {\n // Leave the message undeleted — SQS redelivers after the visibility timeout.\n this.options.onError?.(error, envelope, message);\n }\n }\n\n private async delete(message: SqsMessage): Promise<void> {\n if (!message.ReceiptHandle) return;\n await this.client.deleteMessage({\n QueueUrl: this.queueUrl,\n ReceiptHandle: message.ReceiptHandle,\n });\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@babelqueue/sqs",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Amazon SQS adapter for BabelQueue — canonical-envelope publisher and a URN-routed consumer.",
|
|
5
|
+
"keywords": ["babelqueue", "sqs", "aws", "queue", "polyglot", "messaging"],
|
|
6
|
+
"homepage": "https://babelqueue.com",
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "https://github.com/BabelQueue/babelqueue-node-adapters/issues"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/BabelQueue/babelqueue-node-adapters.git",
|
|
13
|
+
"directory": "packages/sqs"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "Muhammet Şafak <info@muhammetsafak.com.tr>",
|
|
17
|
+
"type": "module",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"import": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"require": {
|
|
25
|
+
"types": "./dist/index.d.cts",
|
|
26
|
+
"default": "./dist/index.cjs"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"main": "./dist/index.cjs",
|
|
31
|
+
"module": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"files": ["dist"],
|
|
34
|
+
"sideEffects": false,
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsup",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"test": "node --import tsx --test test/*.test.ts"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@babelqueue/core": "^1.0.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@aws-sdk/client-sqs": "^3.0.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"@aws-sdk/client-sqs": {
|
|
51
|
+
"optional": true
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
}
|
|
57
|
+
}
|