Queue
SQS with a dead-letter queue and partial-batch-failure handling, always on.
Declaring a queue
import { Queue } from "@vorynza/wisp";
interface FulfillmentMessage {
orderId: string;
}
export const fulfillment = new Queue<FulfillmentMessage>("fulfillment", {
batchSize: 5,
visibilityTimeout: 60,
dlq: { maxReceiveCount: 2 },
});| Option | Type | Description |
|---|---|---|
fifo | boolean | Optional. Defaults to false (a standard queue). |
batchSize | number | Optional. Max messages per consumer invocation. Defaults to 10 — SQS's own event-source-mapping ceiling. |
visibilityTimeout | number | Optional, in seconds. Defaults to 30. |
maxConcurrency | number | Optional. Caps concurrent consumer invocations. |
dlq.maxReceiveCount | number | Optional. Defaults to 3. |
A dead-letter queue is always created — it's the one part of Queue that isn't optional. A message that fails
maxReceiveCount times lands there instead of retrying forever.
Sending messages
await fulfillment.send({ orderId: order.id });Consuming messages
A queue has at most one consumer, registered with .consume():
export const consumer = fulfillment.consume({ memory: 512 }, async (messages) => {
const failedMessageIds: string[] = [];
for (const message of messages) {
try {
await processOrder(message.body);
} catch {
failedMessageIds.push(message.id);
}
}
return { failedMessageIds };
});The partial-batch-failure contract
This is the part SQS makes easy to get wrong, so wisp's runtime implements it for you:
- Return nothing (or
undefined) — the whole batch succeeded, every message is deleted. - Return
{ failedMessageIds: [...] }— exactly those messages are retried (and eventually sent to the DLQ); the rest are deleted. - Throw — the entire batch is reported failed. This is the safe default: an uncaught exception gives no information about which specific message caused it, so retrying all of them is the only sound choice.
What this deploys to
An AWS::SQS::Queue plus its dead-letter queue, and — if you registered a consumer — an
AWS::Lambda::EventSourceMapping connecting the queue to that Lambda. SQS is poll-based, so there's no
AWS::Lambda::Permission involved the way there is for API Gateway or S3 events. A consumer's IAM role gets
sqs:ReceiveMessage, sqs:DeleteMessage, and sqs:GetQueueAttributes, scoped to this queue's ARN — granted
automatically just by being registered as the consumer, since receiving is inherent to that registration. A function
that only calls .send() gets sqs:SendMessage and nothing else.

