Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/iamolegga/nestjs-saga

Implementation of saga pattern for NestJS
https://github.com/iamolegga/nestjs-saga

nest nestjs

Last synced: 2 months ago
JSON representation

Implementation of saga pattern for NestJS

Awesome Lists containing this project

README

        

# nestjs-saga



npm


npm


GitHub branch checks state





Known Vulnerabilities


Libraries.io

Dependabot

Basic implementation of saga pattern for NestJS (do not confuse it with the [built-in sagas](https://docs.nestjs.com/recipes/cqrs#sagas)).

This module is not too much related to [microservices sagas](https://microservices.io/patterns/data/saga.html) but could be used as a base to implement it.

Highly inspired by [node-sagas](https://github.com/SlavaPanevskiy/node-sagas) but rewritten a bit for more convenient usage with NestJS.

## installation

```sh
npm i nestjs-saga @nestjs/cqrs
```

## usage

### define

```ts
import { Builder, Saga } from 'nestjs-saga';

class FooSagaCommand {
constructor(
public bar: string,
public baz: number,
) {}
}

class FooSagaResult {
// ...
}

@Saga(FooSagaCommand)
class FooSaga {
// Define `saga` field using Builder or if you want to return
// some value use: Builder
saga = new Builder()

// Add a step with the name, invocation and compensation functions
.step('do something')
.invoke(this.step1)
.withCompensation(this.step1Compensation)

// Add another one, name and compensation could be omitted
.step()
.invoke(this.step2)

// If builder with result type is used (Builder) then it's
// required to add last `return` step, final `build` step will be available
// only after this one. If no result type provided in Builder then this
// method won't be available in types and saga will return `undefined`
.return(this.buildResult)

// After all steps `build` should be called
.build();

// Each invocation and compensation methods are called with the command as an
// argument
step1(cmd: FooSagaCommand) {

// Each time saga is called as a new instance, so it's safe to save it's
// state in own fields
this.step1Result = 42;
}

// If step throws error then compensation chain is started in a reverse order:
// step1 -> step2 -> step3(X) -> compensation2 -> compensation1
step2(cmd: FooSagaCommand) {
if (this.step1Result != 42) throw new Error('oh no!');
}

// After all compensations are done `SagaInvocationError` is thrown. It will
// wrap original error which can be accessed by `originalError` field
step1Compensation(cmd: FooSagaCommand) {

// If one of compensations throws error then compensations chain is stopped
// and `SagaCompensationError` is thrown. It will wrap original error which
// can be accessed by `originalError` field
if (this.step1Result != 42) throw new Error('oh no!');
}

// If saga should return some result pass it's type to the Builder generic and
// use `return` method in the build chain with a callback that returns this
// class or type
buildResult(cmd: FooSagaCommand): Result | Promise {
return new Result();
}
}
```

### register

```ts
import { CqrsModule } from '@nestjs/cqrs';
import { SagaModule } from 'nestjs-saga';

@Module({
imports: [
CqrsModule,
SagaModule.register({
imports: [...], // optional
providers: [...], // optional
sagas: [FooSaga, BarSaga, BazSaga], // required
}),
],
})
class AppModule {}
```

### run

```ts
import { CommandBus } from '@nestjs/cqrs';
import { SagaInvocationError, SagaCompensationError } from 'nestjs-saga';

class AnyServiceOrController {
constructor(private commandBus: CommandBus) {}

someMethod() {
try {
// If saga defined with the result type, then result will be passed,
// otherwise it's `undefined`
const result = await this.commandBus.execute(new FooSagaCommand(...args));

} catch (e) {
if (e instanceof SagaInvocationError) {
// Saga failed but all compensations succeeded.
e.originalError // could be used to get access to original error
e.step // can be used to understand which step failed

} else if (e instanceof SagaCompensationError) {
// Saga failed and one of compensations failed.
e.originalError // could be used to get access to original error
e.step // can be used to understand which step compensation failed
}
}
}
}
```

Do you use this library?
Don't be shy to give it a star! ★

Also if you are into NestJS you might be interested in one of my other NestJS libs.