Files
suaveplan/packages/api-stack/api/api-events-in-memory/src/bus/bus.md
2026-05-27 23:29:53 +01:00

4.7 KiB

createInMemoryEventsBus

(no submodules)

Purpose

createInMemoryEventsBus is the factory entry point of @suaveplan/api-events-in-memory. It returns a fresh {@link InMemoryEventsBus} whose publish calls are recorded in a per-topic ring of payloads and whose subscriber delivery is delegated to a private @suaveplan/events EventBus. Tests can both assert "this event was published with this payload" (via published(topic)) and assert "this subscriber observed the side-effect" (via the subscriber callback) without any mocks or test doubles — the real production bus runs underneath.

Features

  • Recording layer wraps every publish / publishAsync / emit call; the payload is appended to the topic's history BEFORE dispatch so history is still complete even if a subscriber throws.
  • Strict-throw history-overflow policy via the cap-bounded {@link History}. published(topic)'s "every publish since reset()" guarantee survives long-running test suites.
  • Subscriber surface (on / once / off) mirrors @suaveplan/events' EventBus; the more powerful EventBus.emitAsync semantics are exposed through publishAsync (sequential await over registered listeners; same delivery order; same error-capture model).
  • emit(topic, payload) alias satisfies the structural CapabilityEventBusLike contract from @suaveplan/api-types/capabilities so the bus can be handed to API plugins as their events capability without an additional adapter.
  • dispose() clears history AND tears down every subscriber so test isolation is straightforward — call once per test, no leaks.
  • createInMemoryEventBus (no "s") is a stable alias matching the prompt's ./adapter subpath; both spellings will continue to exist.

Usage: Basic

import { createInMemoryEventsBus } from "@suaveplan/api-events-in-memory/bus";

const bus = createInMemoryEventsBus();
const seen: unknown[] = [];
bus.on("billing.invoice.created", (payload) => seen.push(payload));

bus.publish("billing.invoice.created", { invoiceId: "inv_1", amount: 100 });

console.log(bus.published("billing.invoice.created"));
// [{ invoiceId: "inv_1", amount: 100 }]
console.log(seen);
// [{ invoiceId: "inv_1", amount: 100 }]

bus.reset();
console.log(bus.published("billing.invoice.created")); // []

Usage: Advanced

Pin the per-topic cap when the test publishes in a tight loop, then recover by reset() between batches:

import { createInMemoryEventsBus } from "@suaveplan/api-events-in-memory/bus";
import {
    EVENTS_IN_MEMORY_CODES,
    EventsInMemoryError,
} from "@suaveplan/api-events-in-memory/error-codes";

const bus = createInMemoryEventsBus({ maxHistoryPerTopic: 3 });

async function fanOut(count: number) {
    for (let i = 0; i < count; i += 1) {
        await bus.publishAsync("metrics.tick", { i });
    }
}

try {
    await fanOut(5);
} catch (error) {
    if (
        error instanceof EventsInMemoryError &&
        error.code === EVENTS_IN_MEMORY_CODES.HISTORY_OVERFLOW
    ) {
        bus.reset();
        await fanOut(2);
    } else {
        throw error;
    }
}

console.log(bus.published("metrics.tick").length); // 2
bus.dispose();

API Reference

  • createInMemoryEventsBus(options?: InMemoryEventsBusOptions): InMemoryEventsBus — factory.
  • createInMemoryEventBus — alias, same return value.
  • InMemoryEventsBus.publish(topic, payload): void — sync publish; appends history first.
  • InMemoryEventsBus.publishAsync(topic, payload): Promise<void> — async publish.
  • InMemoryEventsBus.emit(topic, payload): Promise<void> — CapabilityEventBusLike alias.
  • InMemoryEventsBus.on(topic, listener): Unsubscribe — register a subscriber.
  • InMemoryEventsBus.once(topic, listener): Unsubscribe — one-shot subscriber.
  • InMemoryEventsBus.off(topic, listener): void — remove a subscriber.
  • InMemoryEventsBus.published(topic): readonly unknown[] — snapshot of history.
  • InMemoryEventsBus.topics(): readonly string[] — sorted topic names.
  • InMemoryEventsBus.reset(): void — clear history; keep subscribers.
  • InMemoryEventsBus.dispose(): void — clear history AND subscribers.

Implementation Notes

The underlying EventBus is parameterised with the open EventMap shape (Record<string, unknown>) so the in-memory adapter can carry any topic name without forcing the test to declare a typed map up front. Recording happens BEFORE dispatch because the contract that matters for tests is "this publish was attempted" — history that drops on subscriber failure would silently hide test bugs. emit and publishAsync share the same body; emit exists only so the bus satisfies the structural CapabilityEventBusLike interface from @suaveplan/api-types.