wispwisp
Primitives

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 },
});
OptionTypeDescription
fifobooleanOptional. Defaults to false (a standard queue).
batchSizenumberOptional. Max messages per consumer invocation. Defaults to 10 — SQS's own event-source-mapping ceiling.
visibilityTimeoutnumberOptional, in seconds. Defaults to 30.
maxConcurrencynumberOptional. Caps concurrent consumer invocations.
dlq.maxReceiveCountnumberOptional. 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.

On this page