--- status: draft last_updated: 2026-05-08 --- # Redis Event Target **Import**: `@alkdev/pubsub/event-target-redis` **Peer dep**: `ioredis@^5.0.0` (optional) **Status**: Implemented. Needs tests. Adapted from `@graphql-yoga/redis-event-target` (MIT). ## `createRedisEventTarget` ```ts function createRedisEventTarget( args: CreateRedisEventTargetArgs, ): TypedEventTarget; ``` ### `CreateRedisEventTargetArgs` | Field | Type | Required | Description | |-------|------|----------|-------------| | `publishClient` | `Redis \| Cluster` | Yes | ioredis client for publishing. Can share a connection. | | `subscribeClient` | `Redis \| Cluster` | Yes | ioredis client for subscribing. Must be dedicated — Redis requires subscriber connections to only receive messages. | | `serializer` | `{ stringify, parse }` | No | Custom serializer. Defaults to `JSON`. | ## How It Works - `dispatchEvent` → `publishClient.publish(event.type, serializer.stringify(event.detail))` - `addEventListener` → `subscribeClient.subscribe(topic)`, track callbacks per topic - `removeEventListener` → remove callback; if no callbacks remain for topic, `subscribeClient.unsubscribe(topic)` - On message: deserializes with `serializer.parse`, reconstructs `CustomEvent(channel, { detail: envelope })` The `detail` of the `CustomEvent` dispatched to local listeners is the full `EventEnvelope` object (`{ type, id, payload }`). ## Channel Naming Currently uses the topic string directly as the Redis channel name (e.g., `call.responded:uuid-123`). Should support a configurable prefix: `createRedisEventTarget({ ..., prefix: "alk:events:" })`. ## Limitations (Current) - **No error handling** — connection failures, reconnection, and message parse errors are not handled - **No channel prefix** — raw event types as channel names risk collision in shared Redis instances - **No unsubscribe cleanup on client disconnect** — if the subscribe client disconnects, registered callbacks remain in the map but will never fire - **In-flight messages after unsubscribe** — if `removeEventListener` triggers an `unsubscribe` while a Redis message is in flight, the message may arrive after the callback is removed. This is harmless (the callback set is empty, so the message is a no-op) but worth noting for implementers ## Test Coverage No tests yet. Need: 1. **Publish path** — dispatchEvent sends to Redis with correct channel and serialized envelope 2. **Subscribe path** — addEventListener subscribes to Redis, onMessage dispatches to local listeners 3. **Unsubscribe** — removeEventListener unsubscribes from Redis when no listeners remain for a topic 4. **Topic scoping** — type:id topics are correctly formed 5. **Envelope serialization** — full `{ type, id, payload }` round-trips through JSON 6. **Multiple listeners** — multiple listeners on same topic, single Redis subscribe 7. **Error propagation** — what happens on connection failure