From harness-claude
Builds event-driven NestJS systems using EventEmitter2 for pub/sub side effects and CQRS module with CommandBus/QueryBus. Use for decoupling operations, CQRS patterns, and event sourcing.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Build event-driven systems with EventEmitter2, CQRS module, CommandBus, and QueryBus
Implements CQRS patterns with Python templates for commands, handlers, events, queries, and projectors. Use for separating read/write models, event-sourcing, or scaling queries.
Guides use of Node.js EventEmitter for typed pub-sub communication, memory leak prevention, and event-driven patterns in decoupled modules and TypeScript apps.
Guides implementation of event-driven architecture using domain events, event sourcing, CQRS, outbox patterns, and message brokers for decoupled, reliable systems.
Share bugs, ideas, or general feedback.
Build event-driven systems with EventEmitter2, CQRS module, CommandBus, and QueryBus
npm install @nestjs/event-emitter
@Module({ imports: [EventEmitterModule.forRoot()] })
export class AppModule {}
@Injectable()
export class UsersService {
constructor(private eventEmitter: EventEmitter2) {}
async create(dto: CreateUserDto): Promise<User> {
const user = await this.prisma.user.create({ data: dto });
this.eventEmitter.emit('user.created', new UserCreatedEvent(user));
return user;
}
}
@Injectable()
export class EmailNotificationListener {
@OnEvent('user.created')
async handleUserCreated(event: UserCreatedEvent): Promise<void> {
await this.mailService.sendWelcomeEmail(event.user.email);
}
@OnEvent('user.*') // wildcard
async handleAnyUserEvent(event: unknown): Promise<void> { ... }
}
npm install @nestjs/cqrs
// command
export class CreateUserCommand { constructor(public dto: CreateUserDto) {} }
// handler
@CommandHandler(CreateUserCommand)
export class CreateUserHandler implements ICommandHandler<CreateUserCommand> {
constructor(private usersRepo: UsersRepository) {}
async execute(command: CreateUserCommand): Promise<User> {
return this.usersRepo.create(command.dto);
}
}
// dispatch from controller
@Post()
create(@Body() dto: CreateUserDto) {
return this.commandBus.execute(new CreateUserCommand(dto));
}
// query
@QueryHandler(GetUserQuery)
export class GetUserHandler implements IQueryHandler<GetUserQuery> {
async execute(query: GetUserQuery): Promise<User> {
return this.usersReadRepo.findById(query.id);
}
}
Register all handlers in providers.
EventEmitter2 vs CQRS: EventEmitter2 is in-process pub/sub — simple, zero setup overhead, good for side effects. CQRS is a structural pattern that separates commands (writes) from queries (reads) — use it when you need independent scaling of read/write paths or when the read model differs significantly from the write model.
EventEmitter2 async handlers: @OnEvent('user.created', { async: true }) ensures async handlers do not block the event loop. Set promisify: true on EventEmitterModule.forRoot() to await async handlers before continuing.
EventBus (CQRS): In addition to CommandBus and QueryBus, the CQRS module provides EventBus for domain events. IEventHandler implementations react to events published via eventBus.publish(new UserCreatedEvent(user)).
Saga pattern: @nestjs/cqrs supports long-running processes via @Saga() decorator — these are RxJS streams that react to events and dispatch commands.
Cross-service events: EventEmitter2 is in-process only. For cross-service messaging (microservices), use the NestJS microservices transport layer (@EventPattern, Redis/RabbitMQ) or integrate with Kafka/NATS for durable event streams.
https://docs.nestjs.com/recipes/cqrs