saga-transaction-lib
v1.0.2
Published
A TypeScript library for implementing the Saga pattern to manage distributed transactions and complex workflows
Downloads
34
Maintainers
Readme
Saga Transaction Library
A TypeScript library for implementing the Saga pattern to manage distributed transactions and complex workflows.
Features
- 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
Installation
npm install saga-transaction-lib
Configuration
TypeScript Configuration
To use the decorators in this library, you need to enable experimental decorators in your tsconfig.json
:
{
"compilerOptions": {
"experimentalDecorators": true
... other options
}
}
Basic Usage
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);
}
}
Advanced Usage
Using Decorators
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.
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
}
}
Custom Logger
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
}
}
Custom Error Handler
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
}
}
NestJS Saga Module
// 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
}
}
Best Practices
- 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
Example Usage
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.
License
MIT
Contributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests.
Support
If you have any questions or need help, please open an issue on GitHub.