Kaneo WebSocket Broadcasting Adapters: In-Memory and Redis Implementation Guide

Kaneo provides two concrete WebSocket broadcasting adapters—the InMemoryBroadcastAdapter for single-instance deployments and the RedisBroadcastAdapter for distributed production environments—both implementing the BroadcastAdapter interface to enable real-time event distribution across clients.

The open-source project management platform Kaneo relies on WebSocket connections to deliver real-time task updates and notifications. To accommodate different infrastructure requirements, Kaneo WebSocket broadcasting adapters abstract the message distribution layer through a clean TypeScript interface that supports both local development and scaled production deployments.

Understanding the BroadcastAdapter Interface

At the core of Kaneo's real-time system lies the BroadcastAdapter interface defined in apps/api/src/ws/broadcast-adapter.ts. This contract standardizes how events propagate from the server to connected WebSocket clients across different deployment scenarios.

The interface requires three asynchronous methods:

  • broadcast(event: Event): Promise<void> – Dispatches an event to all adapters subscribed to the event's specific channel.
  • subscribe(channel: string, adapter: WSAdapter): Promise<void> – Registers a WSAdapter (WebSocket connection wrapper) to receive messages for a given channel.
  • unsubscribe(channel: string, adapter: WSAdapter): Promise<void> – Removes a WSAdapter from a channel's subscription list.

Both concrete implementations import the Event type from @kaneo/api/events and the WSAdapter type from @kaneo/api/ws, ensuring type safety across the broadcast layer.

In-Memory Broadcast Adapter for Local Development

The InMemoryBroadcastAdapter located in apps/api/src/ws/in-memory-broadcast-adapter.ts provides a lightweight, process-local implementation ideal for development environments and unit testing. This adapter maintains a private Map<string, Set<WSAdapter>> called channelMap, where each key represents a channel name and each value contains the set of active WebSocket connections subscribed to that channel.

When broadcast(event) is called, the adapter retrieves the adapter set for event.channel and iterates through each connection, invoking adapter.send(event) directly. No external dependencies are required, making this implementation perfect for single-instance deployments where all clients connect to the same server process.

import { InMemoryBroadcastAdapter } from "@kaneo/api/ws";
import { Event } from "@kaneo/api/events";

const adapter = new InMemoryBroadcastAdapter();
const mockWS = { send: async (e: Event) => console.log(e) };

await adapter.subscribe("project-123", mockWS);

const event: Event = {
  channel: "project-123",
  data: { taskId: "456", action: "updated" }
};

await adapter.broadcast(event);

The adapter automatically cleans up empty channels when the last subscriber unsubscribes, preventing memory leaks in long-running development servers.

Redis Broadcast Adapter for Production Scaling

For distributed deployments where multiple Kaneo API instances run behind a load balancer, the RedisBroadcastAdapter in apps/api/src/ws/redis-broadcast-adapter.ts enables cross-process event propagation using Redis Pub/Sub. This implementation wraps a RedisClientType instance created via the redis package's createClient function.

The constructor accepts a redisUrl string and establishes the Redis connection immediately. The adapter maintains local subscription state in a subscriptions Map while leveraging Redis to distribute events across all API instances. When broadcast(event) executes, it serializes the event to JSON and publishes it to the Redis channel matching event.channel. Incoming Redis messages trigger the private dispatch(event) method, which forwards the deserialized event to locally connected WebSocket adapters.

import { RedisBroadcastAdapter } from "@kaneo/api/ws";
import { Event } from "@kaneo/api/events";

const redisUrl = process.env.REDIS_URL || "redis://localhost:6379";
const adapter = new RedisBroadcastAdapter(redisUrl);

// In instance A:
await adapter.subscribe("project-123", wsAdapter);

// In instance B:
const event: Event = {
  channel: "project-123", 
  data: { message: "Task completed" }
};
await adapter.broadcast(event);
// Instance A receives the event through Redis and forwards to its local clients

The adapter manages Redis subscriptions efficiently, calling client.subscribe(channel) only when the first local adapter subscribes to a channel, and client.unsubscribe(channel) when the last local adapter leaves.

Selecting the Right Adapter for Your Deployment

Choosing between the two Kaneo WebSocket broadcasting adapters depends entirely on your infrastructure topology:

InMemoryBroadcastAdapter excels in containerized local development, continuous integration pipelines, and single-node deployments where horizontal scaling is unnecessary. The test suite in tests/api/ws/in-memory-broadcast-adapter.test.ts demonstrates its usage with Vitest and mock adapters.

RedisBroadcastAdapter becomes essential when running multiple Kaneo API instances behind a load balancer or across Kubernetes pods. Without Redis, events broadcast from Instance A would never reach WebSocket clients connected to Instance B.

Both implementations maintain identical method signatures and behavior semantics, allowing you to switch between them by changing a single configuration line without modifying business logic.

Summary

  • The BroadcastAdapter interface in apps/api/src/ws/broadcast-adapter.ts defines the contract for real-time event distribution with broadcast, subscribe, and unsubscribe methods.
  • InMemoryBroadcastAdapter stores subscriptions in a local Map and serves single-instance deployments and testing scenarios.
  • RedisBroadcastAdapter leverages Redis Pub/Sub to synchronize events across multiple Kaneo API instances for horizontal scalability.
  • Both adapters use the same Event and WSAdapter types from the @kaneo/api package, ensuring type-safe interoperability.

Frequently Asked Questions

What is the BroadcastAdapter interface in Kaneo?

The BroadcastAdapter interface is the abstraction layer that decouples Kaneo's WebSocket event system from specific message distribution implementations. Defined in apps/api/src/ws/broadcast-adapter.ts, it requires three methods: broadcast to send events, subscribe to register clients to channels, and unsubscribe to remove them. This design allows the API server to switch between in-memory and Redis-based broadcasting without changing application code.

How does the InMemoryBroadcastAdapter handle WebSocket subscriptions?

The InMemoryBroadcastAdapter maintains a private channelMap of type Map<string, Set<WSAdapter>> where each channel maps to a set of active WebSocket connections. When subscribe(channel, adapter) is called, it adds the adapter to the set for that channel, creating a new set if necessary. The broadcast method retrieves the adapter set for the event's channel and calls send(event) on each connection sequentially.

When should I use the RedisBroadcastAdapter instead of the in-memory version?

Use the RedisBroadcastAdapter whenever you deploy multiple instances of the Kaneo API behind a load balancer or across different servers. Without Redis, WebSocket clients connected to Instance A would not receive events broadcast from Instance B because the in-memory adapter cannot share state across process boundaries. The Redis adapter solves this by publishing events to a shared Redis channel that all instances subscribe to.

Can I implement a custom broadcast adapter for Kaneo?

Yes, any class implementing the BroadcastAdapter interface from apps/api/src/ws/broadcast-adapter.ts will work with Kaneo's WebSocket system. Your custom implementation must provide the three async methods: broadcast(event), subscribe(channel, adapter), and unsubscribe(channel, adapter), handling the exact type signatures using Event from @kaneo/api/events and WSAdapter from @kaneo/api/ws. This flexibility allows integration with alternative message brokers like RabbitMQ or Apache Kafka if needed.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →