first commit
This commit is contained in:
162
docs/api-stack/api/api-ws-broker-redis/README.md
Normal file
162
docs/api-stack/api/api-ws-broker-redis/README.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# @suaveplan/api-ws-broker-redis - Redis pub/sub broker for @suaveplan/api-ws — cross-host WebSocket and SSE fan-out. Publishes BrokerEnvelopes via Redis Pub/Sub channels, maintains per-(tenant,topic) reconnect event-buffers in sorted-sets with cap + TTL, gzip-compresses payloads above a configurable threshold, exposes a healthz probe + auto-reconnect, and emits the standard ws.broker.* OTel surface. Ships a minimal built-in RESP client so integration tests can drive a real Redis container without an external client dependency; accepts any pluggable RedisPubSubLike (e.g. ioredis or node-redis) in production.
|
||||
|
||||
Cross-host Redis pub/sub broker for `@suaveplan/api-ws` and `@suaveplan/api-sse`.
|
||||
|
||||
Implements the `WsBroker` contract from `@suaveplan/api-ws` against a real
|
||||
Redis instance: every `broker.broadcast(topic, envelope)` is published to a
|
||||
shared Redis channel pattern (default `ws.broadcast.*`), and every host
|
||||
subscribed to that pattern delivers the broadcast to its locally-connected
|
||||
subscribers via the local `ConnectionRegistry`. Connections are NOT migrated
|
||||
across hosts — the broker just makes broadcasts visible everywhere.
|
||||
|
||||
The same broker serves the reconnect event-buffer required by `api-ws-bun`
|
||||
(WebSocket) and `api-sse-bun` (SSE): a per-`(tenant, topic)` Redis sorted-set
|
||||
storing the most-recent `eventBufferMaxEvents` events with TTL
|
||||
`eventBufferTtlSec`, queryable by `Last-Event-Id` for `WS` reconnect or
|
||||
`Last-Event-Id` header for SSE resume.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [broker](./src/broker/broker.md) — This module is the orchestrator for `@suaveplan/api-ws-broker-redis`. It wires two Redis clients (one in publish mode,…
|
||||
- [broker](./src/broker/broker.md) — This module is the orchestrator for `@suaveplan/api-ws-broker-redis`. It wires two Redis clients (one in publish mode,…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — This module defines the stable error-code registry for `@suaveplan/api-ws-broker-redis`. Every failure surface the brok…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — This module defines the stable error-code registry for `@suaveplan/api-ws-broker-redis`. Every failure surface the brok…
|
||||
- [event-buffer](./src/event-buffer/event-buffer.md) — This module backs the broker's reconnect event buffer. Each `(tenant, topic)` pair maps to one Redis sorted-set keyed b…
|
||||
- [event-buffer](./src/event-buffer/event-buffer.md) — This module backs the broker's reconnect event buffer. Each `(tenant, topic)` pair maps to one Redis sorted-set keyed b…
|
||||
- [health](./src/health/health.md) — This module is the pure, side-effect-free evaluator for the broker's health status. It inspects the two underlying RESP…
|
||||
- [health](./src/health/health.md) — This module is the pure, side-effect-free evaluator for the broker's health status. It inspects the two underlying RESP…
|
||||
- [instruments](./src/instruments/instruments.md) — This module owns the OpenTelemetry instrument surface for the Redis broker. Six instruments cover every documented oper…
|
||||
- [instruments](./src/instruments/instruments.md) — This module owns the OpenTelemetry instrument surface for the Redis broker. Six instruments cover every documented oper…
|
||||
- [options](./src/options/options.md) — This module defines the configuration shape consumed by `createRedisWsBroker(...)`. Every operational knob the broker e…
|
||||
- [options](./src/options/options.md) — This module defines the configuration shape consumed by `createRedisWsBroker(...)`. Every operational knob the broker e…
|
||||
- [publish](./src/publish/publish.md) — This module handles wire-encoding for the Redis broker. Every published frame gets a one-byte encoding tag (`ENCODING_J…
|
||||
- [publish](./src/publish/publish.md) — This module handles wire-encoding for the Redis broker. Every published frame gets a one-byte encoding tag (`ENCODING_J…
|
||||
- [reconnect](./src/reconnect/reconnect.md) — This module is the deterministic policy evaluator for the broker's reconnect loop. It computes the next backoff delay i…
|
||||
- [reconnect](./src/reconnect/reconnect.md) — This module is the deterministic policy evaluator for the broker's reconnect loop. It computes the next backoff delay i…
|
||||
- [resp-client](./src/resp-client/resp-client.md) — This module ships the broker's built-in RESP-protocol client so the package has no mandatory runtime dependency on an e…
|
||||
- [resp-client](./src/resp-client/resp-client.md) — This module ships the broker's built-in RESP-protocol client so the package has no mandatory runtime dependency on an e…
|
||||
- [subscribe](./src/subscribe/subscribe.md) — This module owns the broker's local routing table — the in-process map from topic name to handler set that the inbound…
|
||||
- [subscribe](./src/subscribe/subscribe.md) — This module owns the broker's local routing table — the in-process map from topic name to handler set that the inbound…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## What ships
|
||||
|
||||
| Subpath | Module | Doc |
|
||||
|---|---|---|
|
||||
| `./broker` | `createRedisWsBroker(opts)` factory | [`src/broker/broker.md`](src/broker/broker.md) |
|
||||
| `./error-codes` | `WS_BROKER_REDIS_CODES` + `WsBrokerRedisError` | [`src/error-codes/error-codes.md`](src/error-codes/error-codes.md) |
|
||||
| `./options` | option schema + defaults | [`src/options/options.md`](src/options/options.md) |
|
||||
| `./resp-client` | built-in minimal RESP client (`createRespClient`) | [`src/resp-client/resp-client.md`](src/resp-client/resp-client.md) |
|
||||
|
||||
## Surface
|
||||
|
||||
- `createRedisWsBroker(opts)` — async factory returning a `RedisWsBroker`
|
||||
(the `WsBroker` contract plus `writeBuffer`, `readBuffer`, `healthz`, and
|
||||
`isDisposed`).
|
||||
- `RedisWsBrokerOptions` — Zod-validated strict option shape. Required:
|
||||
`connection`. Optional: `keyPrefix`, `channelPrefix`, `eventBufferTtlSec`,
|
||||
`eventBufferMaxEvents`, `compressionThresholdBytes`, `reconnectStrategy`,
|
||||
`commandTimeoutMs`, `disableCompression`, `hostId`.
|
||||
- `WS_BROKER_REDIS_CODES` — closed set of 10 stable error codes
|
||||
(`CONNECTION_LOST`, `PUBLISH_FAILED`, `SUBSCRIBE_FAILED`,
|
||||
`BUFFER_WRITE_FAILED`, `BUFFER_READ_FAILED`, `PAYLOAD_TOO_LARGE`,
|
||||
`DESERIALISE_FAILED`, `CONFIG_INVALID`, `RECONNECT_FAILED`, `TIMEOUT`).
|
||||
- `createRespClient(opts)` — minimal built-in RESP client implementing
|
||||
`RedisPubSubLike`. Use this when you do not already have an `ioredis` or
|
||||
`node-redis` instance in your runtime.
|
||||
- `RedisPubSubLike` — pluggable client interface; pass any conforming
|
||||
implementation via `clientFactory` to use `ioredis` / `node-redis` /
|
||||
another adapter.
|
||||
|
||||
## Quickstart
|
||||
|
||||
```ts
|
||||
import { createRedisWsBroker } from "@suaveplan/api-ws-broker-redis";
|
||||
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();
|
||||
```
|
||||
|
||||
## Operational notes
|
||||
|
||||
### Key-prefix conventions
|
||||
|
||||
The broker namespaces every Redis key under `<keyPrefix>` (default
|
||||
`suaveplan:ws:`). Within that namespace it owns:
|
||||
|
||||
- `<keyPrefix>buffer:<tenant>:<topic>` — sorted-set holding the last
|
||||
`eventBufferMaxEvents` entries with scores keyed on `eventId`. Member
|
||||
format is `<eventId>|<base64-payload>`.
|
||||
|
||||
Pub/sub channels are namespaced under `<channelPrefix>` (default
|
||||
`ws.broadcast.`): `<channelPrefix><topic>`.
|
||||
|
||||
Two `<keyPrefix>` choices must be in lock-step across every host replica
|
||||
that participates in the same fan-out fabric — otherwise broadcasts will
|
||||
land on different channel names and never be delivered.
|
||||
|
||||
### Redis memory sizing
|
||||
|
||||
The peak Redis memory consumed by a single broker is bounded by:
|
||||
|
||||
```text
|
||||
hosts * tenants * topics * eventBufferMaxEvents * average_event_size
|
||||
```
|
||||
|
||||
with TTL `eventBufferTtlSec` ensuring abandoned topics expire on their own.
|
||||
For default settings (cap 256, TTL 5 min, ~1 KiB events) that is
|
||||
~256 KiB per active `(tenant, topic)`. A 10k-active-topic deployment
|
||||
budgets ~2.5 GiB of Redis memory for buffers alone, before pub/sub
|
||||
traffic. Tune the cap and TTL down for memory-constrained deployments.
|
||||
|
||||
### Multi-host topology
|
||||
|
||||
Every host eagerly `PSUBSCRIBE`s `<channelPrefix>*` at construction time
|
||||
so the first publish is immediately visible to peers. Adding a host is
|
||||
purely additive — no configuration change to existing hosts is required;
|
||||
the new host comes up, subscribes to the pattern, and starts delivering
|
||||
broadcasts to its locally-connected subscribers.
|
||||
|
||||
### Compression
|
||||
|
||||
Payloads above `compressionThresholdBytes` (default 1 KiB) are
|
||||
gzipped before publish. The wire format carries a one-byte encoding
|
||||
magic so subscribers can decode either form transparently. The 4 MiB
|
||||
hard cap (`MAX_PAYLOAD_BYTES`) refuses runaway payloads with
|
||||
`WS_BROKER_REDIS.PAYLOAD_TOO_LARGE`.
|
||||
|
||||
### Pluggable clients
|
||||
|
||||
The default broker opens two sockets via the built-in RESP client
|
||||
(`./resp-client`). Pass `clientFactory: (url, role) => myAdapter(url, role)`
|
||||
to swap in an `ioredis` or `node-redis` adapter — the adapter must satisfy
|
||||
the {@link RedisPubSubLike} interface.
|
||||
|
||||
## Quality bars
|
||||
|
||||
- 100% line / branch / function / statement coverage (Istanbul).
|
||||
- 100% docs coverage — every implementation `.ts` has a sibling `.md` with
|
||||
the required headings.
|
||||
- Zero direct imports of `vitest` / `bun:test` — all test files import
|
||||
from `@suaveplan/testing/runner`.
|
||||
- Stable, namespaced error codes (`WS_BROKER_REDIS.*`).
|
||||
- Real-Redis integration tests via `@suaveplan/testing-integration-redis`
|
||||
with the testcontainers-backed `RedisFixture` (gracefully skipped when
|
||||
Docker is unavailable).
|
||||
|
||||
Reference in New Issue
Block a user