first commit
This commit is contained in:
124
docs/api-stack/api/api-events-in-memory/src/bus/bus.md
Normal file
124
docs/api-stack/api/api-events-in-memory/src/bus/bus.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# `createInMemoryEventsBus`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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
|
||||
|
||||
```ts
|
||||
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:
|
||||
|
||||
```ts
|
||||
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`.
|
||||
Reference in New Issue
Block a user