Skip to main content
The @talosjs/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) and subscribers receive the same typed payload on the channel, so components communicate without knowing about each other, even across separate processes or server instances.

What it gives you

Publishers and subscribers share only a channel name and a data shape, never a direct reference, which keeps the two sides decoupled. Each event class is generic over its Data type, and that type carries through publish and into the handler. Everything for one event (getChannel(), handler(), publish(), and subscribe()) lives in a single RedisPubSub subclass. Because messages travel through Redis, every server instance subscribed to a channel receives them. You register the class with a decorator and resolve it, client already injected, from the container.

How it works

You extend the abstract RedisPubSub 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. 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

The client reads 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.
Resolve the event from the container, subscribe once at startup, and publish whenever the event occurs:
The publisher and the subscriber can live in different processes. As long as both resolve an event with the same channel, the message published by one reaches the other’s handler through Redis. A channel can be dynamic. Compute it in getChannel() to scope subscriptions per tenant, room, or user:

Exceptions

The component throws EventException when the client cannot connect or an operation fails. It carries a machine-readable key, a human-readable message, and a data object.

Working with channels

Tie each channel to a single Data type so publishers and subscribers stay in agreement. Subscribe once during bootstrap rather than per request, and call unsubscribe() on shutdown. Keep payloads serializable: data round-trips through JSON, so use scalar fields and steer clear of class instances, functions, and circular references. Handle errors inside handler so a single bad message doesn’t stop processing; the handler should catch and route its own failures. Name channels with a stable scheme such as user-signed-up or user:123:events, which keeps even dynamic channels predictable. Let the container provide RedisPubSubClient instead of constructing connections by hand.

CLI command

Scaffold an event class and its test file with the generator. It writes the class under modules/<module>/src/events/<Name>Event.ts, registers it in the module, and installs @talosjs/event if it is missing.
The generated class extends RedisPubSub with the client injected, ready for you to type the payload and fill in the handler:
See event:create for the full command reference.

Use with Claude and Codex

The generator ships a matching event: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:
Then ask Claude in natural language. It maps the request to the generator, runs it, and fills in the implementation:
Prompt
For example, the prompt above maps to event:create --name=UserSignedUp --channel=user-signed-up, then types the payload and implements handler().