Rabbit Relay

EventEnvelope

Canonical message format used by Rabbit Relay for all published and consumed messages.

EventEnvelope is the canonical message format used by Rabbit Relay.

All published and consumed messages use this shape.


Type definition

type EventEnvelope<T = unknown> = {
  id: string;
  name: string;
  v: string;
  time: number;
  data: T;
  meta?: EventMeta;
};

type EventMeta = {
  corrId?: string;
  causationId?: string;
  headers?: Record<string, string>;

  expectsReply?: boolean;
  timeoutMs?: number;
};

Fields

FieldMeaning
idUnique event ID. Useful for idempotency and de-duplication
nameEvent name, also used as the default routing key
vEvent version, for example v1
timeEvent creation time in epoch milliseconds
dataTyped payload
metaOptional metadata for headers, tracing, and RPC

Creating envelopes

Use an event factory:

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

const orderCreated = event("order.created", "v1").of<{
  orderId: string;
  amount: number;
}>();

const ev = orderCreated({
  orderId: "o-1",
  amount: 42,
});

Factories ensure:

  • consistent envelope shape
  • generated IDs
  • consistent timestamps
  • typed payloads

Metadata helpers

Rabbit Relay provides helpers so you do not need to mutate meta manually.

Add headers

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

const ev = withHeaders(orderCreated({ orderId: "o-1", amount: 42 }), {
  tenantId: "tenant-1",
  source: "orders-service",
});

Add metadata

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

const ev = withMeta(orderCreated({ orderId: "o-1", amount: 42 }), {
  corrId: "corr-123",
  headers: {
    tenantId: "tenant-1",
  },
});

Add correlation or causation

import {
  withCorrelation,
  withCausation,
} from "@bitspacerlabs/rabbit-relay";

const ev1 = withCorrelation(orderCreated(data), "corr-123");
const ev2 = withCausation(orderCreated(data), "parent-event-id");

AMQP headers on consumed events

Rabbit Relay copies AMQP headers into event.meta.headers before invoking your handler.

This is useful for:

  • retry metadata
  • delayed retry metadata
  • redrive metadata
  • RabbitMQ x-death headers
  • application headers

Example:

sub.handle("orders.created", async (_id, ev) => {
  console.log(ev.meta?.headers?.["x-rabbit-relay-retry-count"]);
  console.log(ev.meta?.headers?.["x-rabbit-relay-redrive-count"]);
});

Tracing child events

Use traceFrom(parent) when one event causes another event.

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

const child = paymentRequested(
  {
    orderId: ev.data.orderId,
    amount: ev.data.amount,
  },
  traceFrom(ev)
);

This:

  • preserves the parent corrId
  • uses parent.id as causationId
  • copies parent headers
  • lets you add extra headers
const child = paymentRequested(data, traceFrom(ev, {
  headers: {
    source: "payments-service",
  },
}));

RPC metadata

RPC is enabled through metadata internally.

You can still do this:

ev.meta = {
  expectsReply: true,
  timeoutMs: 5000,
};

But the cleaner API is:

const reply = await pub.request<Reply>(ev, {
  timeoutMs: 5000,
});

Notes

  • EventEnvelope is plain JSON
  • Delivery remains at-least-once
  • Consumers should be idempotent
  • Keep payloads small and publish references for large data

Summary

EventEnvelope is the stable contract between publishers and consumers.

Factories and helpers make it safer to create, enrich, trace, and publish events.

On this page