Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9.6 KiB
@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.
Documentation tree
- broker — This module is the orchestrator for
@suaveplan/api-ws-broker-redis. It wires two Redis clients (one in publish mode,…- broker — This module is the orchestrator for
@suaveplan/api-ws-broker-redis. It wires two Redis clients (one in publish mode,…
- broker — This module is the orchestrator for
- error-codes — This module defines the stable error-code registry for
@suaveplan/api-ws-broker-redis. Every failure surface the brok…- error-codes — This module defines the stable error-code registry for
@suaveplan/api-ws-broker-redis. Every failure surface the brok…
- error-codes — This module defines the stable error-code registry for
- event-buffer — This module backs the broker's reconnect event buffer. Each
(tenant, topic)pair maps to one Redis sorted-set keyed b…- event-buffer — This module backs the broker's reconnect event buffer. Each
(tenant, topic)pair maps to one Redis sorted-set keyed b…
- event-buffer — This module backs the broker's reconnect event buffer. Each
- health — This module is the pure, side-effect-free evaluator for the broker's health status. It inspects the two underlying RESP…
- health — This module is the pure, side-effect-free evaluator for the broker's health status. It inspects the two underlying RESP…
- instruments — This module owns the OpenTelemetry instrument surface for the Redis broker. Six instruments cover every documented oper…
- instruments — This module owns the OpenTelemetry instrument surface for the Redis broker. Six instruments cover every documented oper…
- options — This module defines the configuration shape consumed by
createRedisWsBroker(...). Every operational knob the broker e…- options — This module defines the configuration shape consumed by
createRedisWsBroker(...). Every operational knob the broker e…
- options — This module defines the configuration shape consumed by
- publish — This module handles wire-encoding for the Redis broker. Every published frame gets a one-byte encoding tag (`ENCODING_J…
- publish — This module handles wire-encoding for the Redis broker. Every published frame gets a one-byte encoding tag (`ENCODING_J…
- reconnect — This module is the deterministic policy evaluator for the broker's reconnect loop. It computes the next backoff delay i…
- reconnect — This module is the deterministic policy evaluator for the broker's reconnect loop. It computes the next backoff delay i…
- resp-client — This module ships the broker's built-in RESP-protocol client so the package has no mandatory runtime dependency on an e…
- resp-client — This module ships the broker's built-in RESP-protocol client so the package has no mandatory runtime dependency on an e…
- subscribe — This module owns the broker's local routing table — the in-process map from topic name to handler set that the inbound…
- subscribe — This module owns the broker's local routing table — the in-process map from topic name to handler set that the inbound…
What ships
| Subpath | Module | Doc |
|---|---|---|
./broker |
createRedisWsBroker(opts) factory |
src/broker/broker.md |
./error-codes |
WS_BROKER_REDIS_CODES + WsBrokerRedisError |
src/error-codes/error-codes.md |
./options |
option schema + defaults | src/options/options.md |
./resp-client |
built-in minimal RESP client (createRespClient) |
src/resp-client/resp-client.md |
Surface
createRedisWsBroker(opts)— async factory returning aRedisWsBroker(theWsBrokercontract pluswriteBuffer,readBuffer,healthz, andisDisposed).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 implementingRedisPubSubLike. Use this when you do not already have anioredisornode-redisinstance in your runtime.RedisPubSubLike— pluggable client interface; pass any conforming implementation viaclientFactoryto useioredis/node-redis/ another adapter.
Quickstart
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 lasteventBufferMaxEventsentries with scores keyed oneventId. 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:
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 PSUBSCRIBEs <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
.tshas a sibling.mdwith 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-rediswith the testcontainers-backedRedisFixture(gracefully skipped when Docker is unavailable).