npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lgse/cqrs

v1.0.0

Published

CQRS components for TypeScript

Readme

CQRS

License: MIT npm Test codecov

A collection of CQRS components for TypeScript:

Inspired by the NestJS CQRS module without a dependency on RxJS.

Installation

npm install @lgse/cqrs

Validation Dependencies (optional)

This library provides validation for commands, events, and queries using class-validator and instantiation using class-transformer.

To use ValidatedCommand, ValidatedEvent, and ValidatedQuery you will need to install class-validator and class-transformer as peer dependencies.

npm install class-validator class-transformer

You will need to modify your tsconfig.json:

{
  "compilerOptions": {
    "emitDecoratorMetadata": true, // required by class-validator
    "experimentalDecorators": true, // required by class-validator
    "strictPropertyInitialization": false, // properties are initialized by the constructor in the abstract classes
  }
}

DI Dependencies (optional)

This library provides a TypediHandlerInstantiator for use with TypeDI.

To use the TypediHandlerInstantiator you will need to install typedi as a dependency.

npm install typedi

Usage

Validation

This library provides two approaches for working with commands, events, and queries:

  1. Validated Classes (ValidatedCommand, ValidatedEvent, ValidatedQuery):

    • Include built-in validation using class-validator and class-transformer
    • Automatically validate data upon instantiation
    • Throw descriptive errors when validation fails
    • Require additional dependencies (class-validator and class-transformer)
  2. Basic Classes (Command, Event, Query):

    • Lightweight with no validation
    • Useful for simple scenarios or when you want to implement your own validation
    • No additional dependencies required

Choose the approach that best fits your application's needs. For most production applications, the validated classes are recommended for better data integrity and error handling.

Handler Instantiation

By default, handlers are instantiated using the instance method on the handler class. This method is defined on the AbstractCommandHandler, AbstractEventHandler, and AbstractQueryHandler classes.

If you wanted to inject dependencies into your handlers, you can provide a custom ICommandHandlerInstantiator, IEventHandlerInstantiator, or IQueryHandlerInstantiator to the bus constructor.

TypeDI Instantiator

To use the TypeDI instantiator, you will need to install typedi as a dependency.

npm install typedi

Then you can use the TypediHandlerInstantiator to instantiate your handlers.

import { CommandBus, TypediHandlerInstantiator } from '@lgse/cqrs';

const bus = new CommandBus({
  instantiator: new TypediHandlerInstantiator(),
});

Command Bus

import {
  AbstractCommandHandler,
  CommandBus,
  ICommand,
  TypediHandlerInstantiator,
  ValidatedCommand,
} from '@lgse/cqrs';
import { IsString, IsUUID } from 'class-validator';
import { Inject, Service } from 'typedi';

class CreateUserCommand extends ValidatedCommand<CreateUserCommand> {
  @IsUUID()
  public id: string;

  @IsString()
  public name: string;
}

@Service()
class UsersRepository {
  public async create(name: string): Promise<void> {
    // create the user
  }
}

@Service()
@CommandHandler(CreateUserCommand)
class CreateUserCommandHandler extends AbstractCommandHandler<CreateUserCommand> {
  @Inject()
  private usersRepository: UsersRepository;

  public async execute(command: CreateUserCommand): Promise<void> {
    await this.usersRepository.create(command.name);
  }
}

const bus = new CommandBus({
  instantiator: new TypediHandlerInstantiator(),
});
bus.register([CreateUserCommandHandler]);

const command = CreateUserCommand.create({
  id: '123e4567-e89b-12d3-a456-426614174000',
  name: 'test',
});

await bus.execute(command);

Event Bus

import { IsUUID } from 'class-validator';
import { AbstractEventsHandler, EventBus, EventsHandler, ValidatedEvent } from '@lgse/cqrs';

class UserCreatedEvent extends ValidatedEvent<UserCreatedEvent> {
  @IsUUID()
  public id: string;
  
  @IsString()
  public name: string;
}

class UserUpdatedEvent extends ValidatedEvent<UserUpdatedEvent> {
  @IsUUID()
  public id: string;

  @IsString()
  public name: string;
}

@EventsHandler(UserCreatedEvent, UserUpdatedEvent)
class UserEventsHandler extends AbstractEventsHandler<UserCreatedEvent | UserUpdatedEvent> {
  public handle(event: UserCreatedEvent | UserUpdatedEvent): Promise<void> {
    // handle the event
  }
}

const bus = new EventBus();
bus.register([UserEventsHandler]);

const event = UserCreatedEvent.create({
  id: '123e4567-e89b-12d3-a456-426614174000',
  name: 'test',
});

const event2 = UserUpdatedEvent.create({
   id: '123e4567-e89b-12d3-a456-426614174000',
   name: 'test',
});

// publish one event at a time
await bus.publish(event);
await bus.publish(event2);

// or publish multiple events at once
await bus.publishAll([event, event2]);

Query Bus

import { IsIn, IsInt } from 'class-validator';
import { QueryBus, QueryHandler, ValidatedQuery } from '@lgse/cqrs';

class GetItemsQuery extends ValidatedQuery<GetItemsQuery, string> {
  @IsIn(['asc', 'desc'])
  public order: 'asc' | 'desc';

  @IsInt()
  public page: number;
}

@QueryHandler(GetItemsQuery)
class GetItemsQueryHandler extends AbstractQueryHandler<GetItemsQuery, string[]> {
  public execute(query: GetItemsQuery): Promise<string[]> {
    return ['item1', 'item2'];
  }
}

const bus = new QueryBus();
bus.register([GetItemsQueryHandler]);

const query = GetItemsQuery.create({
  order: 'asc',
  page: 1,
});

// result type is automatically inferred from the query
const result: string = await bus.execute(query);