EventBus Plugins
withEventBusPrefix plugin
The EventBus prefix plugin intercepts calls to an event bus adapter and transparently prefixes all event names with a configurable string. This enables logical event namespace isolation without modifying the adapter implementation.
Use cases
- Multi-tenant systems — Prefix event names with a tenant identifier to isolate events between tenants
- Environment isolation — Separate development, staging, and production event streams
- Module scoping — Organize events by feature or module to avoid naming collisions
How it works
The withEventBusPrefix function returns a PluginFn that calls enhance on each adapter method that accepts an event name. When an enhanced method is invoked, the plugin intercepts the call, prepends the configured prefix to the event name argument, and forwards the modified arguments to the original method.
The plugin prefixes event names for the following methods:
| Method | Event name argument | Pattern |
|---|---|---|
dispatch | First argument | prefix + key |
addListener | First argument | prefix + key |
removeListener | First argument | prefix + key |
Usage
import { withPlugin } from "eridu-tech/middleware";
import { MemoryEventBusAdapter } from "eridu-tech/event-bus/memory-event-bus-adapter";
import { withEventBusPrefix } from "eridu-tech/event-bus/plugins";
const adapter = new MemoryEventBusAdapter();
// Apply the prefix plugin to the adapter
const prefixedAdapter = withPlugin(adapter, withEventBusPrefix("tenant-42:"));
Before/after behavior
Before — Event names are used as-is:
import type { BaseEvent } from "eridu-tech/event-bus/contracts";
import { MemoryEventBusAdapter } from "eridu-tech/event-bus/memory-event-bus-adapter";
const adapter = new MemoryEventBusAdapter();
// Event data to dispatch and listener to register
const data = { userId: "123" };
const listener = (event: BaseEvent): void => {
console.log("Received event:", event);
};
await adapter.dispatch("user.created", data);
// -> dispatches "user.created"
await adapter.addListener("user.created", listener);
// -> listens to "user.created"
After — Event names are automatically prefixed:
import { withPlugin } from "eridu-tech/middleware";
import { MemoryEventBusAdapter } from "eridu-tech/event-bus/memory-event-bus-adapter";
import { withEventBusPrefix } from "eridu-tech/event-bus/plugins";
import type { BaseEvent } from "eridu-tech/event-bus/contracts";
const adapter = new MemoryEventBusAdapter();
// Apply the prefix plugin to the adapter
const prefixedAdapter = withPlugin(adapter, withEventBusPrefix("tenant-42:"));
// Event data to dispatch and listener to register
const data = { userId: "123" };
const listener = (event: BaseEvent): void => {
console.log("Received event:", event);
};
await prefixedAdapter.dispatch("user.created", data);
// -> dispatches "tenant-42:user.created"
await prefixedAdapter.addListener("user.created", listener);
// -> listens to "tenant-42:user.created"
Because withPlugin uses enhance under the hood, the same edge case applies: if one enhanced method internally calls another enhanced method via this, the middleware will apply twice. Be mindful of inter-method calls when applying plugins that enhance multiple methods on the same instance.
For more information about the withPlugin function and applying plugins to adapters, see the Middleware plugin documentation.
withListenerTracking plugin
The withListenerTracking plugin wraps another plugin with automatic listener-reference tracking. When a middleware plugin intercepts addListener and wraps the listener function, the adapter stores the wrapped reference. If the caller later invokes removeListener with the original listener, the adapter cannot find it — the reference has changed.
This plugin solves that problem by ensuring that removeListener with the original listener correctly resolves through the chain.
Use cases
- Listener reference transparency — Callers can use the original listener function with
removeListenereven when a plugin wraps the listener inaddListener - Plugin safety — Wrap plugins that transform listeners in
addListener(for example a plugin that wraps the listener to add logging or validation) to ensureremoveListenerstill resolves correctly - Per-plugin tracking — Apply
withListenerTrackingto each plugin that wraps listeners; it does not automatically handle wrapping from other plugins in the chain
This plugin is only needed if you call removeListener at runtime. If you only register listeners during startup and never remove them, listener-reference tracking is unnecessary.
How it works
withListenerTracking wraps the provided plugin and adds tracking layers around addListener and removeListener on the adapter. When addListener is called, the original listener is wrapped with a tracking wrapper and the mapping from original to wrapper is stored in a ListenerStore keyed by event name. On removeListener, the original listener is resolved back to the tracking wrapper through the store before forwarding the call down the chain.
The plugin execution order is:
- The user plugin's enhancements are applied first (inner layer)
- The tracking enhancements are applied second (outermost layer)
Usage
import { withPlugin } from "eridu-tech/middleware";
import { MemoryEventBusAdapter } from "eridu-tech/event-bus/memory-event-bus-adapter";
import { withListenerTracking } from "eridu-tech/event-bus/plugins";
import type { PluginFn } from "eridu-tech/middleware/contracts";
import type { IEventBusAdapter } from "eridu-tech/event-bus/contracts";
const adapter = new MemoryEventBusAdapter();
// A plugin that wraps listeners, e.g. to add logging or validation
const loggingPlugin: PluginFn<IEventBusAdapter> = (instance, enhance) => {
enhance(
instance,
"addListener",
({ args: [eventName, listener], next }) => {
return next([
eventName,
(event) => {
console.log(`Received "${eventName}"`);
return listener(event);
},
]);
},
);
};
// Apply listener tracking around a plugin that wraps listeners
const enhancedAdapter = withPlugin(
adapter,
withListenerTracking(loggingPlugin),
);
Chaining multiple tracking calls
Multiple withListenerTracking calls can be composed together:
import { withPlugin } from "eridu-tech/middleware";
import { MemoryEventBusAdapter } from "eridu-tech/event-bus/memory-event-bus-adapter";
import { withListenerTracking } from "eridu-tech/event-bus/plugins";
import type { PluginFn } from "eridu-tech/middleware/contracts";
import type { IEventBusAdapter } from "eridu-tech/event-bus/contracts";
const adapter = new MemoryEventBusAdapter();
// Plugin A: wraps listeners, e.g. to add logging
const pluginA: PluginFn<IEventBusAdapter> = (instance, enhance) => {
enhance(
instance,
"addListener",
({ args: [eventName, listener], next }) => {
return next([
eventName,
(event) => {
console.log(`[A] Received "${eventName}"`);
return listener(event);
},
]);
},
);
};
// Plugin B: another plugin that wraps listeners, e.g. to add validation
const pluginB: PluginFn<IEventBusAdapter> = (instance, enhance) => {
enhance(
instance,
"addListener",
({ args: [eventName, listener], next }) => {
return next([
eventName,
(event) => {
console.log(`[B] Received "${eventName}"`);
return listener(event);
},
]);
},
);
};
// Compose multiple tracking-wrapped plugins
const enhancedAdapter = withPlugin(adapter, [
withListenerTracking(pluginA),
withListenerTracking(pluginB),
]);
Because withPlugin uses enhance under the hood, the same edge case applies: if one enhanced method internally calls another enhanced method via this, the middleware will apply twice. Be mindful of inter-method calls when applying plugins that enhance multiple methods on the same instance.
For more information about the withPlugin function and applying plugins to adapters, see the Middleware plugin documentation.