Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
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
- Host: GitHub
- URL: https://github.com/iamolegga/nestjs-saga
- Owner: iamolegga
- License: mit
- Created: 2022-08-16T11:10:49.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2024-09-10T02:08:25.000Z (4 months ago)
- Last Synced: 2024-09-10T10:17:00.834Z (4 months ago)
- Topics: nest, nestjs
- Language: TypeScript
- Homepage:
- Size: 1.54 MB
- Stars: 19
- Watchers: 3
- Forks: 0
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# nestjs-saga
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.