82 lines
2.9 KiB
Markdown
82 lines
2.9 KiB
Markdown
# `broker` — multi-host fan-out contract
|
|
|
|
<!-- begin:auto-docs -->
|
|
_(no submodules)_
|
|
<!-- end:auto-docs -->
|
|
|
|
## Purpose
|
|
|
|
A single-host deployment never needs a broker — the `SubscriptionBus`
|
|
inside that process already sees every connection. Production deployments
|
|
that run multiple host replicas behind a load balancer DO need one: a
|
|
broadcast issued on host A has to reach the connections attached to host
|
|
B without migrating connections between processes. The `WsBroker` contract
|
|
defined here is the seam those broker adapters implement.
|
|
|
|
Concrete brokers (`api-ws-broker-in-memory` for single-host tests,
|
|
`api-ws-broker-redis` for production) plug behind this interface and stay
|
|
out of plugin code paths.
|
|
|
|
## Features
|
|
|
|
- `BrokerEnvelope` — wire payload (`topic`, `tenant`, `payload`, optional
|
|
`originHost`).
|
|
- `BrokerHandler` — subscriber callback signature.
|
|
- `BrokerSubscription` — disposal handle returned from `subscribe`.
|
|
- `WsBroker<Payload>` interface — `publish`, `subscribe`, `dispose`. Each
|
|
method may return synchronously or asynchronously so in-memory brokers
|
|
stay cheap and Redis-backed brokers can `await` round trips.
|
|
|
|
## Usage: Basic
|
|
|
|
```ts
|
|
import type { WsBroker } from "@suaveplan/api-ws/broker";
|
|
|
|
async function fanOut(broker: WsBroker<{ orderId: string }>): Promise<void> {
|
|
await broker.publish("orders.created", {
|
|
topic: "orders.created",
|
|
tenant: tenantId,
|
|
payload: { orderId: "o1" },
|
|
});
|
|
}
|
|
```
|
|
|
|
## Usage: Advanced
|
|
|
|
```ts
|
|
import type { WsBroker, BrokerSubscription } from "@suaveplan/api-ws/broker";
|
|
|
|
async function bridge(
|
|
broker: WsBroker<unknown>,
|
|
local: { broadcast: (...args: unknown[]) => void },
|
|
): Promise<BrokerSubscription> {
|
|
const subscription = await broker.subscribe("orders.created", (envelope) => {
|
|
local.broadcast(envelope.topic, {
|
|
tenant: envelope.tenant,
|
|
payload: envelope.payload,
|
|
});
|
|
});
|
|
return subscription;
|
|
}
|
|
```
|
|
|
|
## API Reference
|
|
|
|
- `interface BrokerEnvelope<Payload> { topic, tenant, payload, originHost? }`.
|
|
- `type BrokerHandler<Payload> = (env: BrokerEnvelope<Payload>) => void | Promise<void>`.
|
|
- `interface BrokerSubscription { topic; unsubscribe(): void | Promise<void> }`.
|
|
- `interface WsBroker<Payload> { publish, subscribe, dispose }`.
|
|
|
|
## Implementation Notes
|
|
|
|
- The contract intentionally does not specify reliability semantics. Each
|
|
adapter declares its own delivery guarantees (`in-memory` is at-most-
|
|
once with FIFO inside the process; `redis` is at-most-once with Redis
|
|
pub/sub semantics). Plugins that need stronger guarantees use the
|
|
saga / 2PC capabilities elsewhere in the foundation.
|
|
- `originHost` is optional so adapters that don't need loop-back
|
|
suppression can omit it. Adapters that DO suppress loop-back compare
|
|
against the local host's identity at the receive side.
|
|
- `dispose` MUST close every subscription cleanly; the conformance
|
|
harness pins this invariant against every concrete adapter.
|