Rabbit Relay

Configuration

Broker defaults, exchange options, and topology ownership

Rabbit Relay keeps configuration explicit and close to RabbitMQ concepts. You configure defaults on the broker, then override them per queue/exchange interface when needed.

RabbitMQ connection URL

Rabbit Relay reads the connection URL from the RABBITMQ_URL environment variable:

RABBITMQ_URL=amqp://user:password@localhost:5672

Or override it per broker:

const broker = new RabbitMQBroker("orders-service", {
  connectionUrl: "amqp://user:password@rabbitmq.internal:5672",
  connectionName: "orders-service-primary",
  shutdownTimeoutMs: 30_000,
});

Prop

Type

Each broker owns its own connection; closing one broker never closes another broker's resources.

Common topology options

Every .exchange() call accepts these overrides on top of broker defaults:

Prop

Type

Topology ownership mode

Decide who owns RabbitMQ topology:

// App declares everything (default)
const broker = new RabbitMQBroker("orders-service", {
  topologyMode: "assert",
});

Infrastructure-managed deployments

Prefer topologyMode: "passive" over the legacy passiveQueue flag. passiveQueue remains supported but only controls main-queue declaration.

Publisher confirms

Disabled by default. Enable when the publisher must know that RabbitMQ accepted the message:

const pub = await broker.exchange("orders.ex", { publisherConfirms: true });

Message size guard

const broker = new RabbitMQBroker("orders-service", {
  maxMessageBytes: 256 * 1024,
});

// override per publish
await sub.publish(envelope, { maxMessageBytes: 64 * 1024 });

App-owned topology

Use the default mode when the application owns topology.

const broker = new RabbitMQBroker("orders-service", {
  topologyMode: "assert",
});

Rabbit Relay declares:

  • exchange
  • queue
  • binding
  • configured DLQ topology
  • delayed retry topology when used

Infrastructure-owned topology

Use passive mode when topology is created before the app starts.

const broker = new RabbitMQBroker("orders-service", {
  topologyMode: "passive",
});

Rabbit Relay checks that required exchanges and queues exist. It does not declare or bind topology. If required topology is missing, startup fails early.

CI / review topology mode

Use plan-only mode when you want Rabbit Relay to build a topology plan without RabbitMQ topology setup calls.

const broker = new RabbitMQBroker("orders-service", {
  topologyMode: "plan-only",
});

const sub = await broker
  .queue("orders.q")
  .exchange("orders.ex", {
    exchangeType: "topic",
    routingKey: "orders.*",
  });

console.log(broker.planTopology());

This is useful for:

  • CI checks
  • DevOps review
  • docs generation
  • comparing code topology with infrastructure topology

Dead-letter configuration

const sub = await broker
  .queue("orders.q")
  .exchange("orders.ex", {
    deadLetter: {
      exchange: "orders.dlx",
      queue: "orders.dlq",
      routingKey: "orders.dead",
      autoDeclare: true,
    },
  });

When autoDeclare: true, Rabbit Relay includes DLQ topology in assertion and planning behavior.

deadLetter.routingKey sets x-dead-letter-routing-key on the source queue and, when autoDeclare is on, is also used as the DLQ→DLX binding key. Omitting it preserves original routing keys and binds the DLQ to "#".

With topologyMode: "passive", Rabbit Relay checks the configured DLX/DLQ exist instead of declaring them. With topologyMode: "plan-only", Rabbit Relay records them in the topology plan without setup calls.

Native amqplib options

Use amqp when you need RabbitMQ-specific options not directly modeled by Rabbit Relay.

const sub = await broker
  .queue("orders.q")
  .exchange("orders.ex", {
    amqp: {
      queue: {
        arguments: {
          "x-queue-type": "quorum",
        },
      },
      publish: {
        persistent: true,
      },
    },
  });

Using config from plain JavaScript or JSON

A common setup keeps topology config outside TypeScript (a shared .mjs module or JSON file). TypeScript widens literal values there, so they stop matching relay's option unions:

// platform.mjs
export const EXCHANGES = [{ name: "orders.events", type: "topic" }];
// inferred inside TS as: { name: string, type: string }[]

Passing EXCHANGES[0].type to exchangeType then fails typecheck, because the option expects "topic" | "direct" | "fanout" | "headers" and receives string.

Rabbit Relay exports reusable aliases so you never have to hand-copy union members out of .d.ts files. Annotate the config at its source using a JSDoc import type:

// platform.mjs
/** @typedef {import("@bitspacerlabs/rabbit-relay").ExchangeType} ExchangeType */

/** @type {{ name: string, type: ExchangeType }[]} */
export const EXCHANGES = [{ name: "orders.events", type: "topic" }];

In TypeScript files, import the alias directly:

import type { ExchangeType } from "@bitspacerlabs/rabbit-relay";

const type: ExchangeType = EXCHANGES[0].type;

await broker.queue("orders.q").exchange("orders.events", {
  exchangeType: type,
});

Available aliases:

AliasValuesUsed by
ExchangeType"topic" | "direct" | "fanout" | "headers"exchangeType, deadLetter.exchangeType
TopologyMode"assert" | "passive" | "plan-only"topologyMode
ErrorAction"ack" | "requeue" | "dead-letter" | "retry"onError
RetryThenAction"ack" | "requeue" | "dead-letter"retry.then
EnvironmentRecommended mode
Local development"assert"
Tests with disposable RabbitMQ"assert"
CI topology review"plan-only"
Production with app-owned topology"assert"
Production with infra-owned topology"passive"

Summary

  • Configure defaults on RabbitMQBroker
  • Override per .exchange(...)
  • Use topologyMode to make topology ownership explicit
  • Prefer topologyMode: "passive" for infrastructure-managed RabbitMQ
  • Keep passiveQueue only for legacy queue-only passive behavior

On this page