@ooneex/event component is a Redis-backed publish/subscribe layer. You declare an event as a class that extends RedisPubSub, give it a channel name, and implement a handler for incoming messages. Publishers call publish(data); subscribers receive the same typed payload on the channel — so components communicate without knowing about each other, even across separate processes or server instances.
Why this component
- Decoupled communication. Publishers and subscribers only share a channel name and a data shape — never a direct reference.
- Typed payloads. Each event class is generic over its
Datatype, carried through publish and into the handler. - One class per event.
getChannel(),handler(),publish(), andsubscribe()live together in a singleRedisPubSubsubclass. - Distributed by default. Messages travel through Redis, so every server instance subscribed to a channel receives them.
- Container-managed. Register an event class with a decorator and resolve it (with its injected client) from the container.
How it works
You extend the abstractRedisPubSub base class, declare a channel with getChannel(), and implement handler() for messages arriving on that channel. The base class wires publish, subscribe, unsubscribe, and unsubscribeAll to an injected RedisPubSubClient, which talks to Redis and JSON-serializes payloads on the way out and parses them on the way in.
| Member | Purpose |
|---|---|
getChannel() | Return the channel name to publish/subscribe on (sync or async). |
handler(context) | Handle an incoming message; receives { data, channel }. |
publish(data) | Serialize and publish data to the channel. |
subscribe() | Start receiving messages on the channel, routed to handler. |
unsubscribe() | Stop receiving on this event’s channel. |
unsubscribeAll() | Stop receiving on every subscribed channel. |
getChannel() and handler() are the two abstract members you implement; the rest are provided by RedisPubSub. Data types must extend Record<string, ScalarType> so they round-trip cleanly through JSON. Malformed messages that fail to parse are silently ignored by the client.
Environment variables
| Variable | Required | Purpose |
|---|---|---|
PUBSUB_REDIS_URL | Yes (unless passed in options) | Redis connection string, e.g. redis://localhost:6379. Missing throws EventException (CONNECTION_FAILED). |
PUBSUB_REDIS_URL from the app environment when no connectionString is given in its options.
Decorator and usage
@decorator.event()
Registers an event class with the container. It accepts an optional scope (defaults to singleton). The decorated class gets its RedisPubSubClient injected, so you resolve the fully wired event from the container.
handler through Redis.
A channel can be dynamic. Compute it in getChannel() to scope subscriptions per tenant, room, or user:
Exceptions
The component throwsEventException when the client cannot connect or an operation fails. It carries a machine-readable key, a human-readable message, and a data object.
| Key | When |
|---|---|
CONNECTION_FAILED | RedisPubSubClient is created without a connection string or PUBSUB_REDIS_URL. |
PUBLISH_FAILED | Publishing a message to a channel fails. |
SUBSCRIBE_FAILED | Subscribing to a channel fails. |
UNSUBSCRIBE_FAILED | Unsubscribing from a channel fails. |
UNSUBSCRIBE_ALL_FAILED | Unsubscribing from all channels fails. |
Best practices
- One channel, one shape. Keep each channel tied to a single
Datatype so publishers and subscribers stay in agreement. - Subscribe once at startup. Call
subscribe()during bootstrap, not per request, andunsubscribe()on shutdown. - Keep payloads serializable. Data round-trips through JSON; use scalar fields and avoid class instances, functions, and circular references.
- Handle errors inside
handler. A throwing handler should catch and route its own failures — don’t let one bad message stop processing. - Name channels consistently. Use a stable scheme like
user-signed-uporuser:123:eventsso dynamic channels stay predictable. - Inject the client. Let the container provide
RedisPubSubClientrather than constructing connections by hand.
CLI command
Scaffold an event class and its test file with the generator. It writes the class undermodules/<module>/src/events/<Name>Event.ts, registers it in the module, and installs @ooneex/event if it is missing.
| Option | Description | Default |
|---|---|---|
--name | Event class name. The Event suffix is appended automatically. | Prompted if omitted |
--module | Target module the class is generated into. | shared |
--channel | Channel name the event publishes/subscribes on. | Kebab-case of the name |
--override | Overwrite an existing class without prompting. | false |
RedisPubSub with the client injected, ready for you to type the payload and fill in the handler:
Use with Claude and Codex
The generator ships a matchingevent:create skill. It runs the scaffold and then guides your AI agent through completing the event — defining a real Data type, setting the channel in getChannel(), and implementing handler(). Initialize the skills once for your agent:
- Claude
- Codex
Prompt
event:create --name=UserSignedUp --channel=user-signed-up, then types the payload and implements handler().