Files
suaveplan/openspec/archive/2026-05-26-api-ws-broker-redis/proposal.md
2026-05-27 23:29:53 +01:00

5.2 KiB

@suaveplan/api-ws-broker-redis — Redis pub/sub broker for cross-host fan-out

Package: @suaveplan/api-ws-broker-redis Status: PROPOSED Type: new-package Stage: 7 (GraphQL + WebSocket / SSE engines) — see umbrella openspec/changes/api-plugin-foundation/

Why

Per design doc §11.3 single-host deployments use an in-memory broker, but production deployments with multiple host replicas need cross-host broadcast: a bus.broadcast on host A must reach the locally-connected subscribers on host B. Redis pub/sub is the simplest and most operationally familiar transport for this. This package is the foundation's reference cross-host broker — required for any multi-replica deployment. The §11.6 SSE design explicitly reuses the same broker abstraction, so this package serves both api-ws-bun (WebSocket) and api-sse-bun (SSE) without duplication.

Sticky sessions are NOT required because connections are not migrated across hosts — the broker just makes broadcasts visible everywhere; each host delivers to its own locally-connected subscribers.

What changes

Net-new package depending on @suaveplan/api-types, @suaveplan/api-ws (broker contract), @suaveplan/api-sse (also consumes the broker), @suaveplan/error, @suaveplan/logger, @suaveplan/opentelemetry, ioredis (peer), and zod.

  • ADDED: createRedisWsBroker(opts) — factory returning a broker value implementing the WsBroker contract from @suaveplan/api-ws.
  • ADDED: RedisWsBrokerOptions — Zod-validated config: connection (URL or ioredis options), keyPrefix (default suaveplan:ws:), channelPrefix (default ws.broadcast.), eventBufferTtlSec (default 300 = 5 min — pairs with api-ws-bun's reconnect TTL), eventBufferMaxEvents (default 256 per (tenant, topic)), compressionThresholdBytes (default 1024 — payloads above this are gzipped before publish), reconnectStrategy.
  • ADDED: Pub/sub transport — broker.broadcast(topic, { tenant, payload }) publishes to <channelPrefix><topic> on Redis; every subscribed host receives the message and delivers to its locally-connected subscribers (looked up via local ConnectionRegistry).
  • ADDED: Reconnect event-buffer storage — for api-ws-bun when its reconnectEventBufferStrategy resolves to broker-backed, this package owns the event buffer: Redis sorted-set per (tenant, topic), scored by lastEventId, capped at eventBufferMaxEvents, TTL eventBufferTtlSec. SSE consumes the same buffer for Last-Event-Id resume.
  • ADDED: Cross-host topology — every host subscribes to the host-wide channel pattern at start; on plugin install/uninstall the broker is informed so per-plugin topic prefixes can be added/dropped from the subscription set if optimisation is wanted. Default is whole-pattern subscription for simplicity.
  • ADDED: Health probe — broker.healthz() returns broker's connection state; consumed by the host's readiness endpoint.
  • ADDED: WS_BROKER_REDIS_* error codes: WS_BROKER_REDIS_CONNECTION_LOST, WS_BROKER_REDIS_PUBLISH_FAILED, WS_BROKER_REDIS_SUBSCRIBE_FAILED, WS_BROKER_REDIS_BUFFER_WRITE_FAILED, WS_BROKER_REDIS_BUFFER_READ_FAILED, WS_BROKER_REDIS_PAYLOAD_TOO_LARGE, WS_BROKER_REDIS_DESERIALISE_FAILED, WS_BROKER_REDIS_CONFIG_INVALID, WS_BROKER_REDIS_RECONNECT_FAILED, WS_BROKER_REDIS_TIMEOUT.
  • ADDED: OTel instruments: ws.broker.publishes_total { topic, outcome }, ws.broker.publish_duration_ms, ws.broker.receives_total { topic }, ws.broker.buffer_writes_total, ws.broker.buffer_reads_total { hit }, ws.broker.connection_lost_total.

Out of scope

  • A non-Redis broker — design doc §11.3 specifically calls Redis as the reference. Other brokers (NATS, Kafka) can implement the same WsBroker contract in future packages.
  • The WebSocket engine itself — that's api-ws-bun. This broker just makes broadcasts visible across hosts.
  • The SSE engine — api-sse-bun. Consumes this same broker for fan-out + event-buffer.
  • Connection migration across hosts — out of scope by design.

Source mapping

Genesis-original. Source of design intent: docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md.

Requirement Design doc section
Cross-host fan-out via Redis pub/sub §11.3
Sticky sessions NOT required §11.3
Reconnect event-buffer storage in Redis §11.5 (paragraph 4) + §14
SSE reuses same broker §11.6
Health probe §12.6

Verification approach

  • src/broker/redis-broker.test.ts (integration, 30000ms) — uses @suaveplan/testing-integration-redis; covers publish/subscribe round-trip, multi-host fan-out simulation (two broker instances bound to same Redis), reconnect after Redis bounce.
  • src/broker/redis-broker.spec-conformance.test.ts — pins the WsBroker contract conformance suite from api-ws/testing.
  • src/event-buffer/event-buffer.test.ts (integration, 30000ms) — sorted-set ops; TTL expiry; cap enforcement; replay by lastEventId.
  • src/health/health.test.ts — healthz reflects connection state; recovers after reconnect.
  • tests/integration/two-host-broadcast.test.ts (60000ms) — boots two api-ws-bun engines against the same Redis; connects clients to each; broadcasts from one; asserts delivery on both.

Migration impact

None — additive only.