2.9 KiB
2.9 KiB
broker — multi-host fan-out contract
(no submodules)
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, optionaloriginHost).BrokerHandler— subscriber callback signature.BrokerSubscription— disposal handle returned fromsubscribe.WsBroker<Payload>interface —publish,subscribe,dispose. Each method may return synchronously or asynchronously so in-memory brokers stay cheap and Redis-backed brokers canawaitround trips.
Usage: Basic
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
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-memoryis at-most- once with FIFO inside the process;redisis at-most-once with Redis pub/sub semantics). Plugins that need stronger guarantees use the saga / 2PC capabilities elsewhere in the foundation. originHostis 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.disposeMUST close every subscription cleanly; the conformance harness pins this invariant against every concrete adapter.