/ts-cqs

Lightweight Command and Query Separation framework for typescript

Primary LanguageJavaScriptMIT LicenseMIT

CircleCI Test Coverage Maintainability npm version Known Vulnerabilities

ts-cqs

Light Command and Query Separation framework for typescript that uses inversify for dependency injection.

⚠️ Note, this is not a CQRS library

Motivation

Following the single responsibility principle, the command and query separation pattern works well. This library sets out to provide a framework in to replicate the CQS pattern in typescript that uses the dependency injection framework InversifyJs.

There are CQRS libraries such as NestJS/CQRS that provides a framework for this pattern. However, it adheres more to the CQRS pattern with event sourcing and uses the bus (command/query processor) as a way to wire up the dependency injection. This library allows you to wire up the dependencies outside of the processors.

Installation

npm install ts-cqs inversify reflect-metadata --save

The InversifyJS type definitions are included in the inversify npm package.

⚠️ Important! ts-cqs requires has a peer dependency on InversifyJs, please view their readme for their requirements.

Usage

Commands

// TestCommand.ts

import 'reflect-metadata'
import { injectable } from 'inversify'
import { ICommand, ICommandHandler, CommandHandler } from 'ts-cqs'

export class TestCommand implements ICommand<string> {
  constructor(public readonly val: string) {}
}

@injectable()
@CommandHandler(TestCommand)
export class TestCommandHandler implements ICommandHandler<TestCommand> {
  async handle(command: TestCommand): Promise<string> {
    return command.val
  }
}

ICommandHandler.execute is set to return any. In this case, command handlers can chose to return something or not.

// CompositeRoot.ts

import { Container } from 'inversify'
import { TestCommandHandler } from './TestCommand'

const container = new Container()
container.bind(TestCommandHandler).toSelf()

The inversify binding here uses the handler class as the id. See docs

// SomeConsumerClass.ts

import { CommandProcessor } from 'ts-cqs'
import { TestCommand } from './TestCommand'

export class SomeConsumerClass {
  constructor(private commandProcessor: CommandProcessor) {}

  async use(): Promise<string> {
    const testCommand = new TestCommand('some value')
    const commandResult = await this.commandProcessor.execute(testCommand)

    console.log(commandResult) // logs 'some value'
  }
}

Contributing

Tests

npm run test