Beignet is experimental alpha software. The 0.0.x package line is for early
evaluation, and APIs may change between releases while the framework settles.
Redis Pub/Sub EventBusPort provider for Beignet applications.
Use it when a multi-process app needs domain events published by one process to reach listeners registered in another process. Delivery is best-effort: subscribers must be online, Redis does not persist messages for later replay, and the provider does not acknowledge, retry, or dead-letter failed handlers. Use Beignet outbox and jobs for durable side effects that need retry, idempotency, or recovery after downtime.
createRedisEventBusProvider(...) returns the stable
RedisEventBusProvider type. RedisEventBusConfig describes its validated
config; the Zod schema remains internal.
bun add @beignet/provider-event-bus-redis @beignet/core ioredis
You can also wire it into an existing app with the CLI:
beignet provider add event-bus-redis
import { createNextServer, createNextServerLoader } from "@beignet/next";
import { createRedisEventBusProvider } from "@beignet/provider-event-bus-redis";
import { initialPorts } from "@/infra/port-wiring";
import { appContext } from "@/server/context";
import { routes } from "@/server/routes";
// Set environment variables:
// REDIS_EVENT_BUS_URL=redis://localhost:6379
// REDIS_EVENT_BUS_DB=0 (optional)
export const getServer = createNextServerLoader(() =>
createNextServer({
ports: initialPorts,
providers: [createRedisEventBusProvider()],
context: appContext,
routes,
}),
);
The provider contributes ctx.ports.eventBus, the standard Beignet event bus
port, and ctx.ports.redisEventBus as a Redis-specific escape hatch.
The provider reads environment variables with the REDIS_EVENT_BUS_ prefix:
| Variable | Required | Description | Example |
|---|---|---|---|
REDIS_EVENT_BUS_URL |
Yes | Redis connection URL. | redis://localhost:6379 |
REDIS_EVENT_BUS_DB |
No | Redis database number. Defaults to 0. |
0 |
REDIS_EVENT_BUS_CHANNEL_PREFIX |
No | Prefix for Pub/Sub channels. Defaults to beignet:event-bus:. |
my-app:events: |
REDIS_EVENT_BUS_CONNECT_TIMEOUT_MS |
No | Initial connection timeout in milliseconds. Defaults to 5000. |
2000 |
REDIS_EVENT_BUS_MAX_RETRIES_PER_REQUEST |
No | Per-command retry limit before a command rejects. Defaults to 2. |
1 |
REDIS_EVENT_BUS_CONNECT_MAX_ATTEMPTS |
No | Connection attempts before startup fails. Defaults to 3. |
5 |
Use createRedisEventBusProvider(options) when the app should own connection
settings. Defined options override matching REDIS_EVENT_BUS_* environment
variables:
import { createRedisEventBusProvider } from "@beignet/provider-event-bus-redis";
const provider = createRedisEventBusProvider({
channelPrefix: "my-app:events:",
connectTimeoutMs: 2000,
maxRetriesPerRequest: 1,
db: 1,
retryStrategy: (times) => Math.min(times * 100, 1000),
});
Calling createRedisEventBusProvider() with no options uses the env-backed
defaults above.
During startup the provider creates separate publisher and subscriber clients,
connects both eagerly, and fails boot with a redacted connection error when
Redis is unreachable. The default initial connection retry strategy stops
after REDIS_EVENT_BUS_CONNECT_MAX_ATTEMPTS; after a successful connection,
ioredis reconnects with capped exponential backoff and resubscribes to the
channels owned by that subscriber client. Events published while the
subscriber is disconnected are still lost because Redis Pub/Sub has no replay.
If an initial SUBSCRIBE command fails after ioredis exhausts its command
retries, the adapter retries that desired subscription with capped backoff for
as long as at least one local handler remains registered.
The environment-backed provider targets a standalone Redis URL or a managed Redis service that exposes one Redis-compatible endpoint. It opens two TCP connections per app process: one publisher and one subscriber. Include both in the process connection budget.
Every long-lived process that should receive events must install the provider and register its listeners. Ephemeral request runtimes can publish events, but they are not reliable subscriber hosts because Redis only delivers while the connection is online. Run subscriptions in a long-lived web or worker process.
Sentinel and Redis Cluster require constructor options that cannot be expressed
by REDIS_EVENT_BUS_URL. Apps using those topologies should create their own
ioredis clients and pass them to createRedisEventBus(...). Beignet's live
suite does not currently prove Sentinel or Cluster failover semantics, so test
subscription recovery against the exact managed topology before relying on it.
HTTP-only Redis APIs are not compatible because Pub/Sub requires a persistent
Redis connection.
Use createRedisEventBus(...) when your app owns Redis clients:
import Redis from "ioredis";
import { createRedisEventBus } from "@beignet/provider-event-bus-redis";
const eventBus = createRedisEventBus({
publisher: new Redis(process.env.REDIS_EVENT_BUS_URL),
subscriber: new Redis(process.env.REDIS_EVENT_BUS_URL),
channelPrefix: "my-app:events:",
});
Register listeners during startup before handling traffic. The EventBusPort
subscription API is synchronous, while Redis subscription acknowledgement is
asynchronous, so messages published immediately after a new subscription may be
missed during startup races.
publish(...) publishes one Redis Pub/Sub message on
<channelPrefix><event.name>.onHandlerError when provided and are
also recorded as provider instrumentation events; they do not reject the
original publisher.onSubscriberError when provided and recorded as
eventBus.message.failed or eventBus.subscription.failed provider
instrumentation events. Failed subscriptions retry with capped backoff while
their local handlers remain registered; removing the last handler cancels a
pending retry.publish(...) calls reject with the Redis error and record an
eventBus.publish.failed instrumentation event. The provider does not retry
the publish after the configured ioredis command retries are exhausted.eventBus instrumentation watcher is enabled, publisher and
subscriber connection errors from env-backed clients are recorded as
eventBus.connection.failed. When instrumentation is absent or disabled,
Beignet leaves ioredis's error fallback intact so connection failures remain
visible.When side effects must survive process restarts or dependency outages, record the domain event through the Unit of Work outbox and drain it through jobs or outbox-backed listeners.
ctx.ports.redisEventBus exposes:
const health = await ctx.ports.redisEventBus.checkHealth();
ctx.ports.redisEventBus.publisher;
ctx.ports.redisEventBus.subscriber;
ctx.ports.redisEventBus.channelPrefix;
checkHealth() sends cheap PING commands to both Redis clients and returns
metadata suitable for app-owned readiness endpoints. It proves both
connections can reach Redis; it does not prove that every expected listener
has registered its channel subscription.
The provider records successful publish events under the eventBus watcher.
Received messages, failed publishes, connection failures, subscription
failures, and handler failures are recorded as custom provider events. Payloads
are not recorded.
Use @beignet/provider-event-bus-memory for deterministic unit tests and
single-process local tests. Use this Redis provider in integration tests when
the behavior under test specifically depends on cross-process delivery or
Redis connection behavior.
This package includes opt-in live Redis integration tests for cross-instance
delivery, reconnect and automatic resubscription, expected loss while offline,
active-subscription cleanup during shutdown, readiness, instrumentation, and
failure callbacks. The default bun run test command runs the hermetic unit
suite only so the live tests cannot share process state with the mocked
ioredis tests.
Set REDIS_EVENT_BUS_TEST_URL or the shared REDIS_TEST_URL before running
the dedicated live test command:
REDIS_EVENT_BUS_TEST_URL=redis://localhost:6379 bun run test:live