first commit
This commit is contained in:
123
docs/api-stack/api/api-ws-broker-in-memory/src/broker/broker.md
Normal file
123
docs/api-stack/api/api-ws-broker-in-memory/src/broker/broker.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# `broker` — `createInMemoryWsBroker`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
This module implements the `WsBroker` contract from
|
||||
`@suaveplan/api-ws` by maintaining an in-process `Map<topic, Set<handler>>`
|
||||
table and fanning out every `publish(topic, envelope)` call to every
|
||||
subscriber currently registered for that topic. The broker is the
|
||||
single-host reference for the multi-host `api-ws-broker-redis` adapter
|
||||
and the default broker wired into the API testing kit.
|
||||
|
||||
"In-memory" describes the storage substrate, not the maturity level —
|
||||
this broker is production-grade for any deployment that fits on a
|
||||
single host (one HTTP server process, one WS engine). Multi-host
|
||||
topologies need the Redis adapter because in-memory has no
|
||||
cross-process visibility by definition.
|
||||
|
||||
## Features
|
||||
|
||||
- Direct in-process subscription map keyed by topic; O(1) subscribe,
|
||||
unsubscribe, and lookup.
|
||||
- Snapshot-before-dispatch so a handler that unsubscribes mid-fan-out
|
||||
does not skip its peers.
|
||||
- Per-subscriber error isolation — a rejecting handler never aborts
|
||||
sibling deliveries nor surfaces as a rejected `publish` promise.
|
||||
- Two delivery modes via `options.deliveryMode`: `"fire-and-forget"`
|
||||
(default; publish resolves on next microtask) and `"await-all"`
|
||||
(publish awaits every handler).
|
||||
- Idempotent `dispose()` that clears the registry and refuses
|
||||
subsequent `publish` / `subscribe` calls with a typed
|
||||
`WS_BROKER_IN_MEMORY.BROKER_DISPOSED` error.
|
||||
- `subscriberCount(topic)` and `isDisposed()` introspection helpers
|
||||
used by tests, observability adapters, and the testing harness in
|
||||
`./testing`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { createInMemoryWsBroker } from "@suaveplan/api-ws-broker-in-memory/broker";
|
||||
import type { TenantId } from "@suaveplan/api-types/tenant";
|
||||
|
||||
const broker = createInMemoryWsBroker();
|
||||
const sub = broker.subscribe("orders", (envelope) => {
|
||||
console.log("received", envelope.payload);
|
||||
});
|
||||
|
||||
await broker.publish("orders", {
|
||||
topic: "orders",
|
||||
tenant: "t1" as unknown as TenantId,
|
||||
payload: { id: 1 },
|
||||
});
|
||||
|
||||
await sub.unsubscribe();
|
||||
await broker.dispose();
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { createInMemoryWsBroker } from "@suaveplan/api-ws-broker-in-memory/broker";
|
||||
import type { TenantId } from "@suaveplan/api-types/tenant";
|
||||
|
||||
// "await-all" mode: publish waits for every handler to settle, even
|
||||
// async ones. Useful in test scenarios that assert side-effects
|
||||
// immediately after publish resolves.
|
||||
const broker = createInMemoryWsBroker<{ id: number }>({
|
||||
deliveryMode: "await-all",
|
||||
});
|
||||
|
||||
const observed: number[] = [];
|
||||
broker.subscribe("orders", async (env) => {
|
||||
await new Promise((r) => setTimeout(r, 5));
|
||||
observed.push(env.payload.id);
|
||||
});
|
||||
broker.subscribe("orders", async (env) => {
|
||||
observed.push(-env.payload.id);
|
||||
});
|
||||
|
||||
await broker.publish("orders", {
|
||||
topic: "orders",
|
||||
tenant: "t1" as unknown as TenantId,
|
||||
payload: { id: 42 },
|
||||
});
|
||||
// observed contains 42 and -42 in some order; both are present
|
||||
// because publish awaited both handlers before resolving.
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `createInMemoryWsBroker<Payload = unknown>(options?): InMemoryWsBroker<Payload>`
|
||||
— factory.
|
||||
- `interface InMemoryWsBroker<Payload> extends WsBroker<Payload>`
|
||||
- `publish(topic, envelope): Promise<void>`
|
||||
- `subscribe(topic, handler): BrokerSubscription`
|
||||
- `dispose(): void`
|
||||
- `subscriberCount(topic): number` — adapter-specific introspection.
|
||||
- `isDisposed(): boolean` — adapter-specific lifecycle query.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The registry is a `Map<string, Set<RegistryEntry>>`. Topics with
|
||||
zero subscribers are removed from the map by `unsubscribe` so the
|
||||
outer Map does not accumulate dead entries.
|
||||
- Each subscription gets a unique `RegistryEntry` reference even if
|
||||
the same `handler` callback is registered twice — duplicate
|
||||
registrations are allowed and each is independently dispatched and
|
||||
independently disposed.
|
||||
- `publish` snapshots the subscriber set into an array before
|
||||
invoking handlers. Without the snapshot, an unsubscribing handler
|
||||
could shrink the live `Set` mid-iteration and skip its peers.
|
||||
- `dispose()` clears the topic map and flips a `disposed` flag.
|
||||
Subsequent `publish` / `subscribe` throw
|
||||
`WS_BROKER_IN_MEMORY.BROKER_DISPOSED`. `dispose()` itself is a
|
||||
no-op on second call so cleanup helpers can call it
|
||||
defensively.
|
||||
- Handler rejections are individually caught (`.catch(() => {})`) so
|
||||
a misbehaving subscriber cannot poison the outer publish. This
|
||||
matches the multi-host broker's semantics: brokers are fan-out
|
||||
pipes, not error reporters.
|
||||
Reference in New Issue
Block a user