EventBus middlewares
EventBus middlewares let you dispatch events around a wrapped function without coupling the function itself to the event bus. Each factory takes an IEventBusResolver and returns a middleware configured with an event type and a payload invocable. The payload is resolved from the wrapped function's arguments (and, where applicable, its return value or caught error) and dispatched on the selected event bus, while the wrapped function's result (or thrown error) passes through unchanged. If the payload resolves to null (i.e. it doesn't return a value), no event is dispatched.
Initial configuration
To begin using the EventBus middlewares, create and configure an EventBusResolver:
import { EventBusResolver } from "eridu-tech/event-bus";
import { RedisPubSubEventBusAdapter } from "eridu-tech/event-bus/redis-pub-sub-event-bus-adapter";
import { MemoryEventBusAdapter } from "eridu-tech/event-bus/memory-event-bus-adapter";
import { SuperJsonSerde } from "eridu-tech/serde/super-json-serde";
import { Redis } from "ioredis";
const serde = new SuperJsonSerde();
export const eventBusResolver = new EventBusResolver({
adapters: {
memory: new MemoryEventBusAdapter(),
redis: new RedisPubSubEventBusAdapter({
client: new Redis("YOUR_REDIS_CONNECTION_STRING"),
serde,
}),
},
defaultAdapter: "memory",
});
await eventBusResolver.init();
withDispatchBeforeFactory middleware
The before-dispatch middleware emits an event before the wrapped function is invoked. When the wrapped function is called, the middleware resolves the event payload from the function's arguments, dispatches the configured event, and then invokes the wrapped function and returns its result. If the payload resolves to null, no event is dispatched and the wrapped function is invoked directly.
This is useful for emitting lifecycle events such as "about to create a user" or for tracing and auditing when a function starts.
Usage
import { withDispatchBeforeFactory } from "eridu-tech/event-bus/middlewares";
import { use } from "eridu-tech/middleware";
import { eventBusResolver } from "./event-bus.js";
const withDispatchBefore = withDispatchBeforeFactory(eventBusResolver);
const createUser = async (userId: string): Promise<string> => {
// ... create the user
return `user-${userId}`;
};
// Wrap with a "before" dispatch using the default adapter (`memory`)
const wrappedCreateUser = use(
createUser,
withDispatchBefore({
type: "user.before.create",
payload: ({ args }) => ({ userId: args[0] }),
}),
);
// Wrap with a "before" dispatch using a specific adapter (`redis`)
const wrappedCreateUserOnRedis = use(
createUser,
withDispatchBefore.use("redis")({
type: "user.before.create",
payload: ({ args }) => ({ userId: args[0] }),
}),
);
await wrappedCreateUser("123");
// The "user.before.create" event is dispatched before createUser runs
Use withDispatchBefore() to dispatch through the resolver's default adapter (memory in this example), or withDispatchBefore.use("redis")({ ... }) to select a specific adapter.
Here is a complete list of settings for the withDispatchBefore function.
Settings
| Option | Type | Description |
|---|---|---|
type | TEventName | The event name to dispatch when the wrapped function is invoked |
payload | Invocable<[settings: WithDispatchBeforePayloadSettings<TParameters>], TEventMap[TEventName] | void> | An invocable that produces the event payload from the wrapped function's arguments. Returning null skips the dispatch |
withDispatchAfterFactory middleware
The after-dispatch middleware emits an event after the wrapped function resolves. When the wrapped function completes, the middleware resolves the event payload from the function's arguments and its return value, dispatches the configured event, and then returns the wrapped function's result unchanged. If the payload resolves to null, no event is dispatched and the wrapped function's result is returned unchanged.
This is useful for emitting completion events such as "user created" or for recording the outcome of a function.
Usage
import { withDispatchAfterFactory } from "eridu-tech/event-bus/middlewares";
import { use } from "eridu-tech/middleware";
import { eventBusResolver } from "./event-bus.js";
const withDispatchAfter = withDispatchAfterFactory(eventBusResolver);
const createUser = async (userId: string): Promise<string> => {
// ... create the user
return `user-${userId}`;
};
// Wrap with an "after" dispatch using the default adapter (`memory`)
const wrappedCreateUser = use(
createUser,
withDispatchAfter({
type: "user.after.create",
payload: ({ args, returnValue }) => ({
userId: args[0],
name: returnValue,
}),
}),
);
// Wrap with an "after" dispatch using a specific adapter (`redis`)
const wrappedCreateUserOnRedis = use(
createUser,
withDispatchAfter.use("redis")({
type: "user.after.create",
payload: ({ args, returnValue }) => ({
userId: args[0],
name: returnValue,
}),
}),
);
const name = await wrappedCreateUser("123");
// The "user.after.create" event is dispatched after createUser resolves,
// with the return value included in the payload
Use withDispatchAfter() to dispatch through the resolver's default adapter (memory in this example), or withDispatchAfter.use("redis")({ ... }) to select a specific adapter.
Here is a complete list of settings for the withDispatchAfter function.
Settings
| Option | Type | Description |
|---|---|---|
type | TEventName | The event name to dispatch after the wrapped function resolves |
payload | Invocable<[settings: WithDispatchAfterPayloadSettings<TParameters, TReturn>], TEventMap[TEventName] | void> | An invocable that produces the event payload from the wrapped function's arguments and its return value. Returning null skips the dispatch |
withDispatchOnErrorFactory middleware
The on-error middleware emits an event when the wrapped function throws. When the wrapped function fails, the middleware resolves the event payload from the function's arguments and the caught error, dispatches the configured event, and then re-throws the original error so the failure still propagates to the caller. If the payload resolves to null, no event is dispatched and the original error is re-thrown as-is.
This is useful for emitting failure events such as "user creation failed" or for feeding an error-monitoring pipeline without swallowing the exception.
Usage
import { withDispatchOnErrorFactory } from "eridu-tech/event-bus/middlewares";
import { use } from "eridu-tech/middleware";
import { eventBusResolver } from "./event-bus.js";
const withDispatchOnError = withDispatchOnErrorFactory(eventBusResolver);
const createUser = async (userId: string): Promise<string> => {
// ... create the user
throw new Error("boom");
};
// Wrap with an error dispatch using the default adapter (`memory`)
const wrappedCreateUser = use(
createUser,
withDispatchOnError({
type: "user.error",
payload: ({ args, error }) => ({
userId: args[0],
error,
}),
}),
);
// Wrap with an error dispatch using a specific adapter (`redis`)
const wrappedCreateUserOnRedis = use(
createUser,
withDispatchOnError.use("redis")({
type: "user.error",
payload: ({ args, error }) => ({
userId: args[0],
error,
}),
}),
);
try {
await wrappedCreateUser("123");
} catch (error) {
// The "user.error" event is dispatched with the caught error,
// then the original error is re-thrown
}
Use withDispatchOnError() to dispatch through the resolver's default adapter (memory in this example), or withDispatchOnError.use("redis")({ ... }) to select a specific adapter.
Here is a complete list of settings for the withDispatchOnError function.
Settings
| Option | Type | Description |
|---|---|---|
type | TEventName | The event name to dispatch when the wrapped function throws |
payload | Invocable<[settings: WithDispatchOnErrorPayloadSettings<TParameters>], TEventMap[TEventName] | void> | An invocable that produces the event payload from the wrapped function's arguments and the caught error. Returning null skips the dispatch |
withHttpEventBusError middleware
The withHttpEventBusError middleware converts every event bus error thrown by the wrapped endpoint or downstream middleware into an HttpError, so the request fails with a meaningful status code instead of a generic 500. Errors that are not event bus errors are rethrown untouched, and the original error is preserved as the cause.
Usage
import { UnableToDispatchEventBusError } from "eridu-tech/event-bus/contracts";
import { withHttpEventBusError } from "eridu-tech/event-bus/middlewares";
import { HttpRouter, defaultHttpRouterAdapter } from "eridu-tech/http-router";
const router = new HttpRouter({
router: defaultHttpRouterAdapter(),
});
router.use(withHttpEventBusError());
router.endpoint({
url: "/users/1",
method: ["POST"],
handler: async () => {
// A 503 response when the event bus adapter fails
throw UnableToDispatchEventBusError.create("user.created");
},
});
Status codes
| Errors | Status |
|---|---|
UnableToDispatchEventBusError, UnableToAddListenerEventBusError, UnableToRemoveListenerEventBusError | 503 |
Every event bus error is thrown when the underlying adapter fails, so they all map to 503 and the same request can be retried later. The original error is preserved as the cause, and no identifiers are exposed in the response body.
Further information
For further information refer to eridu-tech/event-bus API docs.