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.