Skip to content

EventEnvelope

EventEnvelope is the canonical message format used by Rabbit Relay.

All published and consumed messages use this shape.


Type definition

ts
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:

ts
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

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

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

Add metadata

ts
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

ts
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:

ts
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.

ts
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
ts
const child = paymentRequested(data, traceFrom(ev, {
  headers: {
    source: "payments-service",
  },
}));

RPC metadata

RPC is enabled through metadata internally.

You can still do this:

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

But the cleaner API is:

ts
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.

Released under the MIT License.