Skip to Content
Jumentix DocsPackages@jumentix/message-mediatormessage-mediator usage

@jumentix/message-mediator — usage guide

Responsibility in context

  • Stack layer: messaging / application adapter
  • Owns: in-process and broker-backed publish/subscribe + request mediation
  • Used with: backend use-cases; not a replacement for sdk-websocket-client in browsers
  • Not responsible for: OpenAPI HTTP routing or database persistence

What it is

@jumentix/message-mediator decouples event publishers from handlers and supports request/response over named contracts. The in-memory adapter runs in the browser and unit tests; RabbitMQ and BullMQ adapters target Node servers selected by environment variables.

Why it exists

Junior teams need one pattern for “something happened” (events) and “please do this” (commands/queries) without wiring every module directly. The mediator lets you prototype in-memory in a SPA, then deploy the same handler signatures behind a broker — without rewriting business logic.

Prerequisites

  • Runtime: browser (in-memory) or Node/Bun (in-memory, RabbitMQ, or BullMQ).
  • Prior reading: Getting started and basic async/await in JavaScript.
  • For brokers: RabbitMQ URL or Redis connection env vars (see compileMessageMediator in the package).
  • Optional: shared-contracts for contract naming conventions in larger services.

Glossary

TermMeaning
Integration eventFire-and-forget notification: { name, payload, occurredAt, metadata? }.
MessageRequest payload: { contract, version?, payload, metadata? }.
HandlerFunction registered for a contract — returns IMessageResponse.
MediatorObject implementing publish/subscribe and registerHandler/request.
ContractStable string id for a command or query (e.g. users.create).
In-memory adapterInMemoryMessageMediatorAdapter — no durability, same process only.
Broker adapterRabbitMQ or BullMQ — multi-process, survives restarts (Node only).

Steps

1. Install (< 5 minutes)

bun add @jumentix/message-mediator

2. First success — publish and subscribe (< 15 minutes)

import { InMemoryMessageMediatorAdapter } from '@jumentix/message-mediator';

const mediator = InMemoryMessageMediatorAdapter.compile();
const seen: string[] = [];

mediator.subscribe('demo.ping', async (event) => {
  seen.push(event.name);
});

await mediator.publish({
  name: 'demo.ping',
  payload: { hello: true },
  occurredAt: new Date().toISOString()
});

console.log(seen); // ['demo.ping']

Verify success: seen.length === 1 after publish resolves.

3. Core workflow — request/response

Register a handler, then call request:

mediator.registerHandler('users.create', async (message) => ({
  result: { id: 'user-1', username: message.payload.username }
}));

const response = await mediator.request({
  contract: 'users.create',
  version: '1.0.0',
  payload: { username: 'ana' }
});

if (response.error) throw response.error;
console.log(response.result);

Handlers may be sync or async. Errors returned in response.error do not throw unless your wrapper chooses to.

4. Core workflow — compile by environment

import { compileMessageMediator } from '@jumentix/message-mediator';

// JUMENTIX_MESSAGE_MEDIATOR_ADAPTER=inmemory | rabbitmq | bullmq
const mediator = compileMessageMediator();
Adapter envWhen to use
inmemory (default)Local dev, browser demos, unit tests
rabbitmq / rabbitMulti-service async messaging
bullmq / bullJob queues backed by Redis

Broker adapters require the documented env vars; missing URL throws at compile time with a clear message.

5. Core workflow — timeouts and routing options

const response = await mediator.request(
  { contract: 'billing.charge', payload: { amount: 10 } },
  { timeoutMs: 5000, routeKey: 'billing-primary' }
);

Register handlers with matching routeKey or queueName in IMessageHandlerRegistrationOptions when you need more than one consumer per contract.

6. Full surface — API map

ExportRole
IMessageMediatorFull port type
IEventBuspublish + subscribe only
InMemoryMessageMediatorAdapter.compile()In-process mediator
RabbitMqMessageMediatorAdapterRabbitMQ (Node)
BullMqMessageMediatorAdapterBullMQ (Node)
compileMessageMediator()Env-driven factory
Types: IMessage, IMessageResponse, IIntegrationEvent, MessageHandlerTyping handlers and payloads

Try it in the docs playground

In-memory mediator

Exchange messages between Category and Task domains, then compose a read model through mediator request/response.

const domainMessages = [];
const events = [];
const mediator = api.createInMemory();

const categories = new Map([
  ['work', { id: 'work', name: 'Work', color: '#2563eb' }],
  ['home', { id: 'home', name: 'Home', color: '#16a34a' }]
]);
const tasks = [];

await mediator.subscribe('tasks.created', async (event) => {
  events.push({
    title: event.payload.title,
    categoryId: event.payload.categoryId
  });
});

mediator.registerHandler('categories.get.v1', async (message) => {
  domainMessages.push({
    from: message.metadata.sourceDomain,
    to: 'Categories',
    contract: 'categories.get.v1',
    categoryId: message.payload.id
  });
  return {
    ok: true,
    result: categories.get(message.payload.id) ?? null
  };
});

mediator.registerHandler('tasks.create.v1', async (message) => {
  const task = {
    id: message.payload.id,
    title: message.payload.title,
    categoryId: message.payload.categoryId,
    completed: false
  };
  tasks.push(task);
  await mediator.publish({ name: 'tasks.created', payload: task });
  return { ok: true, result: task };
});

mediator.registerHandler('tasks.board.v1', async () => {
  const cards = await Promise.all(tasks.map(async (task) => {
    const category = await mediator.request({
      contract: 'categories.get.v1',
      payload: { id: task.categoryId },
      metadata: {
        sourceDomain: 'Tasks',
        reason: 'compose task board read model'
      }
    });
    return {
      id: task.id,
      title: task.title,
      categoryName: category.result?.name ?? 'Uncategorized',
      categoryColor: category.result?.color ?? '#64748b'
    };
  }));
  return {
    ok: true,
    result: {
      readModel: 'TaskBoard',
      composedFrom: ['Tasks', 'Categories'],
      cards
    }
  };
});

const response = await mediator.request({
  contract: 'tasks.create.v1',
  payload: {
    id: 'task-1',
    title: 'Wire mediator events',
    categoryId: 'work'
  },
  metadata: { source: 'browser-playground' }
});
const board = await mediator.request({
  contract: 'tasks.board.v1',
  payload: {},
  metadata: { source: 'task-board-page' }
});

return {
  createdTask: response.result,
  board: board.result,
  domainMessages,
  events
};

The playground stub uses topic-style subscribe/publish:

const seen = [];
const mediator = api.createInMemory();
await mediator.subscribe('demo.ping', async (msg) => { seen.push(msg); });
await mediator.publish('demo.ping', { hello: true });

In production code, prefer InMemoryMessageMediatorAdapter.compile() with full event objects and registerHandler / request for commands.

When not to use in-memory

  • Multi-process workers — each process has its own memory; events do not cross process boundaries.
  • Durable queues across deploys — use RabbitMQ or BullMQ adapters on Node.

Keep broker adapters out of browser bundles.

Common errors

SymptomCauseFixVerify success
Handler never runsWrong event name or contract stringMatch strings exactly; log registrationsseen or response.result populated
No handler registered for contract …Missing registerHandlerRegister before requestresponse.error absent
Request hangs until timeoutHandler never resolvesReturn or reject from handler; set timeoutMsResponse within timeout
Events lost after refreshIn-memory adapterSwitch to broker adapter in NodeEvent survives process restart
JUMENTIX_RABBITMQ_URL is requiredRabbit adapter without URLSet env or use inmemory locallycompileMessageMediator() succeeds

Junior checklist (“I can …”)

  • Publish an integration event and handle it with subscribe.
  • Register a handler and complete a request / response round-trip.
  • Explain why in-memory is fine for unit tests but not for multi-worker production.
  • Select the correct adapter via JUMENTIX_MESSAGE_MEDIATOR_ADAPTER.
  • Pass timeoutMs and handle response.error without crashing the caller.
  • Describe the difference between an event (publish) and a command (request).

Next step

Connect HTTP and realtime clients in the REST API guide and Realtime API guide, then return to Getting started for the full communication journey.

Last updated on