A 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
npm install saga-transaction-lib
To use the decorators in this library, you need to enable experimental decorators in your tsconfig.json
:
{
"compilerOptions": {
"experimentalDecorators": true
... other options
}
}
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<MyContext> {
name = 'My Step';
async invoke(context: MyContext): Promise<void> {
// Implementation
}
async compensate(context: MyContext): Promise<void> {
// Compensation logic
}
}
// 3. Create and execute the saga
async function runMyTransaction() {
const saga = new Saga<MyContext>();
const context: MyContext = {
/* ... */
};
const steps: IStep<MyContext>[] = [new MyStep()];
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:
- 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.
import { BeforeInvoke, IStep } from 'saga-transaction-lib';
class MyStep implements IStep<MyContext> {
name = 'My Step';
@BeforeInvoke<MyStep, MyContext>(async ({ instance, context }) => {
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<void> {
// Step implementation
}
@BeforeCompensate()
async compensate(context: MyContext): Promise<void> {
// Compensation logic
}
}
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
}
}
import { IErrorHandler, TransactionContext } from 'saga-transaction-lib';
class CustomErrorHandler<TContext>
implements IErrorHandler<TransactionContext<TContext>>
{
async handleError(
error: Error,
context: TransactionContext<TContext>
): Promise<void> {
// Custom error handling logic
}
}
// saga.module.ts
import { Module, DynamicModule } from '@nestjs/common';
import { Saga, SagaOptions } from 'saga-transaction-lib';
@Module({})
export class SagaModule {
static forRoot<TContext>(options?: SagaOptions<TContext>): DynamicModule {
return {
module: SagaModule,
providers: [
{
provide: 'SAGA',
useFactory: () => new Saga<TContext>(options),
},
],
exports: ['SAGA'],
};
}
}
// app.module.ts
@Module({
imports: [
SagaModule.forRoot<YourContextType>({
logger: new CustomLogger(),
}),
],
})
export class AppModule {}
// your.service.ts
@Injectable()
export class YourService {
constructor(
@Inject('SAGA')
private readonly saga: Saga<YourContextType>
) {}
async executeTransaction() {
// Use saga here
}
}
- Keep steps atomic and focused
- Implement proper compensation logic
- Use meaningful step names
- Handle errors appropriately
- Use typing to ensure compile-time safety
- Use decorators to add pre-execution and pre-compensation logic when needed
- 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.