first commit
This commit is contained in:
130
docs/api-stack/api/api-ws-broker-redis/src/broker/broker.md
Normal file
130
docs/api-stack/api/api-ws-broker-redis/src/broker/broker.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# `broker` — `createRedisWsBroker`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
This module is the orchestrator for `@suaveplan/api-ws-broker-redis`. It wires
|
||||
two Redis clients (one in publish mode, one held in subscribe mode), establishes
|
||||
a `PSUBSCRIBE <channelPrefix>*` pattern, and exposes the `WsBroker` surface plus
|
||||
broker-specific operations (`writeBuffer`, `readBuffer`, `healthz`, `isDisposed`).
|
||||
The broker is the production cross-host adapter for the `@suaveplan/api-ws`
|
||||
contract — the in-memory broker handles single-host fan-out and this package
|
||||
extends that to many hosts by riding Redis pub/sub.
|
||||
|
||||
The broker is intentionally pluggable: a `clientFactory` option lets callers
|
||||
swap in `ioredis`, `node-redis`, or any client implementing
|
||||
{@link RedisPubSubLike}. When unset the broker defaults to the built-in RESP
|
||||
client shipped from `./resp-client`, which keeps the package free of mandatory
|
||||
runtime dependencies on an external Redis client.
|
||||
|
||||
## Features
|
||||
|
||||
- Two Redis sockets, one per role — replies on the publisher socket, async
|
||||
pub/sub frames on the subscriber socket. Redis itself enforces that subscribe
|
||||
sockets reject non-subscribe commands; the broker structure mirrors that
|
||||
constraint exactly.
|
||||
- Eager `PSUBSCRIBE` at construction time so a subsequent `publish` is
|
||||
immediately visible to peers.
|
||||
- Compression-aware envelope encoding via `./publish` (gzip above the
|
||||
configurable threshold, hard 4 MiB cap, magic-byte framing).
|
||||
- Reconnect-friendly event buffer: per-`(tenant, topic)` sorted-set with
|
||||
cap + TTL, consumed by `api-ws-bun` and `api-sse-bun` for `Last-Event-Id`
|
||||
resume.
|
||||
- 10 stable `WS_BROKER_REDIS.*` error codes; every production path attaches
|
||||
the original `cause`.
|
||||
- Idempotent `dispose()` that detaches listeners, `PUNSUBSCRIBE`s the pattern,
|
||||
and quits both sockets.
|
||||
- OTel counter + histogram surface (`ws.broker.*`) wired through the
|
||||
`./instruments` module.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { createRedisWsBroker } from "@suaveplan/api-ws-broker-redis/broker";
|
||||
import type { TenantId } from "@suaveplan/api-types/tenant";
|
||||
|
||||
const broker = await createRedisWsBroker<{ id: number }>({
|
||||
connection: "redis://localhost:6379",
|
||||
});
|
||||
|
||||
const sub = await broker.subscribe("orders", (envelope) => {
|
||||
console.log("delivered", envelope.payload.id);
|
||||
});
|
||||
|
||||
await broker.publish("orders", {
|
||||
topic: "orders",
|
||||
tenant: "t1" as unknown as TenantId,
|
||||
payload: { id: 1 },
|
||||
});
|
||||
|
||||
sub.unsubscribe();
|
||||
await broker.dispose();
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { createRedisWsBroker } from "@suaveplan/api-ws-broker-redis/broker";
|
||||
|
||||
// Multi-host setup with custom prefixes and a fixed reconnect strategy.
|
||||
const broker = await createRedisWsBroker({
|
||||
connection: process.env.REDIS_URL!,
|
||||
keyPrefix: "prod:ws:",
|
||||
channelPrefix: "prod.ws.broadcast.",
|
||||
eventBufferMaxEvents: 512,
|
||||
eventBufferTtlSec: 600,
|
||||
compressionThresholdBytes: 2048,
|
||||
commandTimeoutMs: 2_000,
|
||||
reconnectStrategy: {
|
||||
initialDelayMs: 50,
|
||||
maxDelayMs: 2_000,
|
||||
maxAttempts: 200,
|
||||
multiplier: 2,
|
||||
},
|
||||
hostId: process.env.HOSTNAME ?? "host-unknown",
|
||||
});
|
||||
|
||||
// Pluggable client (ioredis adapter, etc.):
|
||||
const ioredisBroker = await createRedisWsBroker({
|
||||
connection: process.env.REDIS_URL!,
|
||||
clientFactory: (url, role) =>
|
||||
// Wrap an ioredis instance into the RedisPubSubLike shape — production
|
||||
// teams who already use ioredis can avoid maintaining two clients.
|
||||
myIoredisAdapter(url, role),
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `createRedisWsBroker<Payload>(options)` — async factory returning a
|
||||
`RedisWsBroker<Payload>`.
|
||||
- `RedisWsBroker<Payload> extends WsBroker<Payload>` — adds `writeBuffer`,
|
||||
`readBuffer`, `healthz`, `isDisposed`, and `options` (the resolved bundle).
|
||||
- `CreateRedisWsBrokerOptions` — `RedisWsBrokerOptions` plus the factory-only
|
||||
`clientFactory` and `readyTimeoutMs` knobs.
|
||||
- `RedisClientFactory` — `(url, role) => RedisPubSubLike`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The broker normalises all timing through `Date.now()` — production deployments
|
||||
that want a synthetic clock should layer that at the OTel reader level rather
|
||||
than reaching into the broker. The constructor strips factory-only fields
|
||||
(`clientFactory`, `readyTimeoutMs`) before handing the rest to the strict
|
||||
Zod schema so unknown keys still fail fast for the genuine option shape.
|
||||
|
||||
The `waitForReady` helper races the client's `ready` event against a timeout
|
||||
and against `error`/`close`. This means a flaky network at startup surfaces
|
||||
fast (`CONNECTION_LOST`) rather than wedging the broker in an unbuilt state.
|
||||
Once construction returns, every method short-circuits with the same code on
|
||||
post-`dispose` calls — the broker is single-use, like the in-memory variant.
|
||||
|
||||
The subscribe path runs `dispatchInboundFrame` per topic via the local
|
||||
registry, which snapshots the handler set before delivery so a handler that
|
||||
unsubscribes mid-fan-out does not silently skip its peers. Decode failures
|
||||
and per-handler rejections route through the `onError` callback (currently
|
||||
captured into `healthz().lastError`) rather than propagating into the pub/sub
|
||||
loop, because Redis will not stop pushing frames just because one of them
|
||||
was malformed.
|
||||
Reference in New Issue
Block a user