Why amqp-contract?
Define your AMQP contracts once — get type safety, autocompletion, and runtime validation everywhere.
- 🔒 End-to-end type safety — TypeScript knows your message shapes
- 🔄 Reliable retry — Built-in exponential backoff with Dead Letter Queue support
- 📄 AsyncAPI compatible — Generate documentation from your contracts
Quick Example
// contract.ts import { defineContract, defineEventConsumer, defineEventPublisher, defineExchange, defineMessage, defineQueue, defineQueueBinding, } from "@amqp-contract/contract"; import { z } from "zod"; // 1. Define resources with Dead Letter Exchange and retry configuration const ordersExchange = defineExchange("orders"); const ordersDlx = defineExchange("orders-dlx"); const orderProcessingQueue = defineQueue("order-processing", { deadLetter: { exchange: ordersDlx, routingKey: "order.failed" }, retry: { mode: "ttl-backoff", maxRetries: 3, initialDelayMs: 1000 }, // Retry configured at queue level }); // A dead-letter exchange with nothing bound to it drops what it receives, so the // DLQ and its binding are what make `deadLetter` actually keep anything. The // binding key must match the dead-letter routing key set above. const orderDlq = defineQueue("order-processing-dlq"); // 2. Define message with schema validation const orderMessage = defineMessage( z.object({ orderId: z.string(), amount: z.number(), }), ); // 3. Event pattern: publisher broadcasts, consumers subscribe const orderCreatedEvent = defineEventPublisher(ordersExchange, orderMessage, { routingKey: "order.created", }); // 4. Define contract - only publishers and consumers needed // Exchanges, queues, and bindings are automatically extracted export const contract = defineContract({ publishers: { orderCreated: orderCreatedEvent, }, consumers: { processOrder: defineEventConsumer(orderCreatedEvent, orderProcessingQueue), }, // The DLQ is declared but never consumed — standalone topology queues: { orderDlq }, bindings: { orderDlq: defineQueueBinding(orderDlq, ordersDlx, { routingKey: "order.failed" }), }, });
Then use that contract — the worker consumes, the client publishes:
import { TypedAmqpClient } from "@amqp-contract/client"; import { TypedAmqpWorker } from "@amqp-contract/worker"; import { OkAsync } from "unthrown"; import { contract } from "./contract.js"; // 5. Type-safe consuming with automatic retry (configured at queue level) const worker = await TypedAmqpWorker.create({ contract, handlers: { processOrder: ({ payload }) => { console.log(payload.orderId); // ✅ TypeScript knows! return OkAsync(); }, }, urls: ["amqp://localhost"], }).get(); // 6. Type-safe publishing with validation const client = await TypedAmqpClient.create({ contract, urls: ["amqp://localhost"], }).get(); // publish() returns an AsyncResult instead of throwing — awaiting it yields a // Result. create()/close() have an empty error channel (E = never), so .get() // is correct there; publish() still has a modeled error, so extract it with // .getOrThrow() (or handle it with .match()). See the error model guide. await client .publish("orderCreated", { orderId: "ORD-123", // ✅ TypeScript knows! amount: 99.99, }) .getOrThrow(); // 7. Clean up await client.close().get(); await worker.close().get();
▶ For the full runnable version (including the RabbitMQ Docker command), follow the 5-minute quick start.
Installation
Requires Node.js 22.19+.
pnpm add @amqp-contract/contract @amqp-contract/client @amqp-contract/worker unthrown zod
unthrown is exposed in the public types (AsyncResult<void, HandlerError>), so consumers need it directly to construct handler results. zod can be swapped for any Standard Schema library (Valibot, ArkType, …).
Need a local RabbitMQ to try it against?
docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:4-management
Documentation
- Get Started — Get running in 5 minutes
- Core Concepts — Understand the fundamentals
- Examples — Real-world usage patterns
Packages
| Package | Description |
|---|---|
| @amqp-contract/contract | Contract builder and type definitions |
| @amqp-contract/client | Type-safe client for publishing |
| @amqp-contract/worker | Type-safe worker with retry support |
| @amqp-contract/core | Shared runtime: topology setup, connections, telemetry |
| @amqp-contract/asyncapi | AsyncAPI 3.1 generator |
| @amqp-contract/testing | Vitest utilities with a RabbitMQ testcontainer |
Contributing
See CONTRIBUTING.md.
License
MIT