JSPM

@fioc/event-bus

1.0.4
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 4
  • Score
    100M100P100Q40100F
  • License MIT

Type-safe event bus with CQRS support for FIoC. Build event-driven architectures with commands, queries, notifications, and middleware - fully integrated with dependency injection.

Package Exports

  • @fioc/event-bus

Readme

@fioc/event-bus

FIoC Event Bus provides a comprehensive, type-safe event-driven architecture system built on top of @fioc/core.
It enables CQRS (Command Query Responsibility Segregation), domain events, and event-driven patterns with full dependency injection integration — completely reflection-free and decorator-free.

💡 Built for complex event-driven architectures, microservices, and domain-driven design — without reflection or decorators.


🚀 Quick Start

Install via npm, yarn, or pnpm:

npm install @fioc/event-bus @fioc/core

# or

yarn add @fioc/event-bus @fioc/core

# or

pnpm add @fioc/event-bus @fioc/core

📘 Table of Contents


🧩 Core Concepts

The Event Bus package introduces several key abstractions for building event-driven applications:

Concept Description
INotification Domain events that can have multiple handlers
ICommand Commands that perform actions (single handler)
IQuery Queries that retrieve data (single handler)
IEventBus Central service for publishing events and invoking commands/queries
IHandlerMiddleware Interceptors for cross-cutting concerns

Everything is fully typed, DI-integrated, and metadata-aware — no decorators, no reflect-metadata.


🔔 Defining Events & Commands

Notifications (Domain Events)

import { createNotificationDIToken } from "@fioc/event-bus";

// Define notification payload type
export type UserRegisteredPayload = {
  userId: string;
  email: string;
  timestamp: Date;
};

export interface UserRegisteredNotification
  extends INotification<UserRegisteredPayload> {}

// Create notification token
export const UserRegisteredNotificationToken =
  createNotificationDIToken<UserRegisteredNotification>().as("UserRegistered", {
    generics: [
      /* Optional payload token */
    ],
  });

Commands

import { createCommandDIToken } from "@fioc/event-bus";

export type CreateUserCommandPayload = {
  email: string;
  password: string;
  name: string;
};

export interface CreateUserCommand
  extends ICommand<CreateUserCommandPayload, string> {}

export const CreateUserCommandToken =
  createCommandDIToken<CreateUserCommand>().as("CreateUser");

Queries

import { createQueryDIToken } from "@fioc/event-bus";

export type GetUserByIdQueryPayload = {
  userId: string;
};

export interface GetUserByIdQuery
  extends IQuery<GetUserByIdQueryPayload, User> {}

export const GetUserByIdQuery =
  createQueryDIToken<GetUserByIdQuery>().as("GetUserById");

🛠️ Creating Handlers

Notification Handlers (Multiple per Event)

import { createNotificationHandlerDIToken } from "@fioc/event-bus";
import { UserRegisteredNotification } from "./events";

export interface SendWelcomeEmailHandler
  extends INotificationHandler<UserRegisteredNotification> {}

export const SendWelcomeEmailHandlerToken =
  createNotificationHandlerDIToken().as("SendWelcomeEmail", {
    generics: [UserRegisteredNotificationToken], // Needs to specify the notification token
  });

// Implementation
const sendWelcomeEmailHandlerImpl: SendWelcomeEmailHandler = {
  async handle(payload) {
    await emailService.sendWelcomeEmail(event.payload.email);
  },
};

Command Handlers (Single per Command)

import { createCommandHandlerDIToken } from "@fioc/event-bus";
import { CreateUserCommand } from "./commands";

export interface CreateUserHandler extends ICommandHandler<CreateUserCommand> {}

export const CreateUserHandlerToken = createCommandHandlerDIToken().as(
  "CreateUserHandler",
  {
    generics: [CreateUserCommandToken], // Needs to specify the command token
  }
);

// Implementation
const createUserHandlerImpl: CreateUserHandler = {
  async handle(command: { payload: CreateUserCommandPayload }) {
    const user = await userService.createUser(command.payload);
    return user.id; // Must match command result type
  },
};

Query Handlers (Single per Query)

import { createQueryHandlerDIToken } from "@fioc/event-bus";
import { GetUserByIdQuery } from "./queries";

export interface GetUserByIdHandler extends IQueryHandler<GetUserByIdQuery> {}

export const GetUserByIdHandlerToken = createQueryHandlerDIToken().as(
  "GetUserByIdHandler",
  {
    generics: [GetUserByIdQueryToken], // Needs to specify the query token
  }
);

// Implementation
const getUserByIdHandlerImpl: GetUserByIdHandler = {
  async handle(query: { payload: GetUserByIdQueryPayload }) {
    return await userRepository.findById(query.payload.userId);
  },
};

📡 Using the Event Bus

Basic Setup

import { buildDIContainer } from "@fioc/core";
import { EventBusFactory, IEventBusToken } from "@fioc/event-bus";

const container = buildDIContainer()
  .registerFactory(IEventBusToken, EventBusFactory)
  .register(SendWelcomeEmailHandlerToken, sendWelcomeEmailHandler)
  .register(CreateUserHandlerToken, createUserHandler)
  .register(GetUserByIdHandlerToken, getUserByIdHandler)
  .getResult();

const eventBus = container.resolve(IEventBusToken);

Publishing Notifications

// Publish a domain event
await eventBus.publish({
  token: UserRegisteredNotification,
  payload: {
    userId: "123",
    email: "user@example.com",
    timestamp: new Date(),
  },
  createdAt: new Date(),
});

// With different execution strategies
await eventBus.publish(notification, "parallel"); // All handlers run concurrently
await eventBus.publish(notification, "sequential"); // Handlers run one after another
await eventBus.publish(notification, "besteffort"); // Parallel but continue on handler errors

Invoking Commands & Queries

// Invoke a command
const userId = await eventBus.invoke({
  token: CreateUserCommand,
  payload: {
    email: "user@example.com",
    password: "securepassword",
    name: "John Doe",
  },
  createdAt: new Date(),
});

// Invoke a query
const user = await eventBus.invoke({
  token: GetUserByIdQuery,
  payload: { userId: "123" },
  createdAt: new Date(),
});

🔌 Middleware & Interceptors

Middleware allows you to implement cross-cutting concerns like logging, validation, and error handling.

Creating Middleware

import { createMiddlewareDIToken, IQueryToken, ICommandToken } from "@fioc/event-bus";

export interface LoggingMiddleware extends IHandlerMiddleware<IQuery<any> | ICommand<any> ,unknown>

export const LoggingMiddleware = createMiddlewareDIToken().as(
  "LoggingMiddleware",
  {
    generics: [IQueryToken, ICommandToken], // Apply to all commands and queries
  }
);

const loggingMiddleware = {
  async handle(request, next: (request: any) => Promise<any>) {
    console.log("Processing request:", request.token.key);
    const startTime = Date.now();

    try {
      const result = await next(request);
      console.log("Request completed in", Date.now() - startTime, "ms");
      return result;
    } catch (error) {
      console.error("Request failed:", error);
      throw error;
    }
  },
};

Middleware Registration & Ordering

import { MiddleWareOrderToken } from "@fioc/event-bus";

const container = buildDIContainer()
  .register(MiddleWareOrderToken, [LoggingMiddleware, ValidationMiddleware]) // Middleware Order declaration
  .registerFactory(IEventBusToken, EventBusFactory)
  .register(LoggingMiddleware, loggingMiddleware)
  .register(ValidationMiddleware, validationMiddleware)
  .getResult();

Applying Middleware to Specific Event Types

// Apply to all notifications
const NotificationLoggingMiddleware = createMiddlewareDIToken().as(
  "NotificationLogging",
  {
    generics: [INotificationToken], // Apply to all notifications
  }
);

// Apply to specific notification
const UserEventMiddleware = createMiddlewareDIToken().as(
  "UserEventMiddleware",
  {
    generics: [UserRegisteredNotification], // Apply only to user registration
  }
);

// Apply to multiple types
const CommandQueryMiddleware = createMiddlewareDIToken().as(
  "CommandQueryMiddleware",
  {
    generics: [ICommandToken, IQueryToken], // Apply to all commands and queries
  }
);

⚡ Execution Strategies

The event bus supports different execution strategies for notification handlers:

Parallel (Default)

await eventBus.publish(notification, "parallel");
  • All handlers execute concurrently
  • Fastest execution time
  • Use when handlers are independent

Sequential

await eventBus.publish(notification, "sequential");
  • Handlers execute one after another
  • Guaranteed order of execution
  • Use when handlers have dependencies

Best Effort (Default)

const errors = await eventBus.publish(notification, "besteffort");
  • All handlers execute concurrently
  • Continues execution even if some handlers fail
  • Returns array of errors from failed handlers
  • Use for non-critical background tasks


📚 API Reference

Core Tokens

Token Description
IEventBusToken The main event bus service
MiddleWareOrderToken Array defining middleware execution order
INotificationToken Base token for all notifications
ICommandToken Base token for all commands
IQueryToken Base token for all queries
INotificationHandlerToken Base token for notification handlers
ICommandHandlerToken Base token for command handlers
IQueryHandlerToken Base token for query handlers
IHandlerMiddlewareToken Base token for middleware

Factory Functions

Function Description
createNotificationDIToken() Creates notification tokens
createCommandDIToken() Creates command tokens
createQueryDIToken() Creates query tokens
createNotificationHandlerDIToken() Creates notification handler tokens
createCommandHandlerDIToken() Creates command handler tokens
createQueryHandlerDIToken() Creates query handler tokens
createMiddlewareDIToken() Creates middleware tokens

Interfaces

Interface Description
IEventBus Main event bus interface
INotification<T> Domain event interface
ICommand<T, R> Command interface
IQuery<T, R> Query interface
INotificationHandler<T> Notification handler interface
ICommandHandler<T> Command handler interface
IQueryHandler<T> Query handler interface
IHandlerMiddleware<T, R> Middleware interface

🤝 Contributing

Contributions are welcome!
Open an issue or submit a PR on GitHub.

Please include tests for new features or fixes and keep commits focused.


📜 License

Licensed under the MIT License.