A TypeScript library for implementing the Saga pattern to manage distributed transactions and complex workflows
npm install saga-transaction-libA TypeScript library for implementing the Saga pattern to manage distributed transactions and complex workflows.
- TypeScript-first implementation
- Flexible and extensible architecture
- Built-in error handling and compensation
- Customizable logging
- Transaction context management
- Decorators for fine-grained control over step execution and compensation
``bash`
npm install saga-transaction-lib
To use the decorators in this library, you need to enable experimental decorators in your tsconfig.json:
`json`
{
"compilerOptions": {
"experimentalDecorators": true
... other options
}
}
`typescript
import { Saga, IStep } from 'saga-transaction-lib';
// 1. Define your context type
interface MyContext {
// your context properties
}
// 2. Implement your steps
class MyStep implements IStep
name = 'My Step';
async invoke(context: MyContext): Promise
// Implementation
}
async compensate(context: MyContext): Promise
// Compensation logic
}
}
// 3. Create and execute the saga
async function runMyTransaction() {
const saga = new Saga
const context: MyContext = {
/ ... /
};
const steps: IStep
try {
const result = await saga.execute(context, steps);
console.log('Transaction completed successfully');
} catch (error) {
console.error('Transaction failed:', error);
}
}
`
The library provides two decorators for fine-grained control over step execution and compensation:
#### @BeforeInvoke & @BeforeCompensate()
- The @BeforeInvoke decorator checks if the step can proceed by returning true. If it does, the invoke method is executed.
- The @BeforeCompensate decorator does not require any condition checks and will directly execute the compensation logic when necessary.
`typescript
import { BeforeInvoke, IStep } from 'saga-transaction-lib';
class MyStep implements IStep
name = 'My Step';
@BeforeInvoke
console.log(Preparing to execute ${instance.name});
// Perform checks or preparations
return true; // Return true to proceed, false to skip this step
})
async invoke(context: MyContext): Promise
// Step implementation
}
@BeforeCompensate()
async compensate(context: MyContext): Promise
// Compensation logic
}
}
`
`typescript
import { ILogger } from 'saga-transaction-lib';
class CustomLogger implements ILogger {
log(message: string): void {
// Custom implementation
}
error(message: string, error?: Error): void {
// Custom implementation
}
warn(message: string): void {
// Custom implementation
}
debug(message: string): void {
// Custom implementation
}
}
`
`typescript
import { IErrorHandler, TransactionContext } from 'saga-transaction-lib';
class CustomErrorHandler
implements IErrorHandler
{
async handleError(
error: Error,
context: TransactionContext
): Promise
// Custom error handling logic
}
}
`
`typescript
// saga.module.ts
import { Module, DynamicModule } from '@nestjs/common';
import { Saga, SagaOptions } from 'saga-transaction-lib';
@Module({})
export class SagaModule {
static forRoot
return {
module: SagaModule,
providers: [
{
provide: 'SAGA',
useFactory: () => new Saga
},
],
exports: ['SAGA'],
};
}
}
// app.module.ts
@Module({
imports: [
SagaModule.forRoot
logger: new CustomLogger(),
}),
],
})
export class AppModule {}
// your.service.ts
@Injectable()
export class YourService {
constructor(
@Inject('SAGA')
private readonly saga: Saga
) {}
async executeTransaction() {
// Use saga here
}
}
`
1. Keep steps atomic and focused
2. Implement proper compensation logic
3. Use meaningful step names
4. Handle errors appropriately
5. Use typing to ensure compile-time safety
6. Use decorators to add pre-execution and pre-compensation logic when needed
7. Ensure isBreakStep is properly managed in your step implementations
A detailed example can be found in the main.ts file located in the example folder. This example demonstrates how to implement a transaction step using both the @BeforeInvoke and @BeforeCompensate` decorators.
MIT
Contributions are welcome! Please read our contributing guidelines and submit pull requests.
If you have any questions or need help, please open an issue on GitHub.