npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

nestjs-transaction

v0.1.7

Published

A library that extracts and provides only some of the functions of the 'typeorm-transactional' npm module that are needed to operate in the Nestjs + TypeORM environment

Downloads

49

Readme

Nestjs Transactional

npm version

It's a fork of typeorm-transactional for Nestjs customization

A Transactional Method Decorator for typeorm that uses ALS to handle and propagate transactions between different repositories and service methods.

To facilitate the use of typeorm-transactional in Nest.js, several features have been added, including the TransactionModule, and the @toss/nestjsaop library is being used to provide transaction capabilities that can be customized based on injectable providers in future services.

Installation

## npm
npm install --save nestjs-transaction

## Needed dependencies
npm install --save typeorm reflect-metadata

Or

yarn add nestjs-transaction

## Needed dependencies
yarn add typeorm reflect-metadata

Note: You will need to import reflect-metadata somewhere in the global place of your app - https://github.com/typeorm/typeorm#installation

Usage

New versions of TypeORM use DataSource instead of Connection, so most of the API has been changed and the old API has become deprecated.

Register TransactionModule with AppModule. Once the module is registered, it automatically finds all TypeORM DataSources that exist and adds them to the Transactional DataSource so that @Transactional can be used.

Example for Nest.js:

// app.module.ts
import { Module } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { TransactionModule } from 'nestjs-transactional;

@Module({
  imports: [
    TransactionModule.forRoot()
  ],
})
export class AppModule {}

Unlike typeorm-transactional-cls-hooked, you do not need to use BaseRepositoryor otherwise define repositories.

NOTE You can select specific DataSource if you need it

Using Transactional Decorator

  • Every service method that needs to be transactional, need to use the @Transactional() decorator
  • The decorator can take a connectionName as argument (by default it is default) to specify the data source to be user
  • The decorator can take an optional propagation as argument to define the propagation behaviour
  • The decorator can take an optional isolationLevel as argument to define the isolation level (by default it will use your database driver's default isolation level)
export class PostService {
  constructor(readonly repository: PostRepository);

  @Transactional() // Will open a transaction if one doesn't already exist
  async createPost(id, message): Promise<Post> {
    const post = this.repository.create({ id, message });
    return this.repository.save(post);
  }
}

You can also use DataSource/EntityManager objects together with repositories in transactions:

export class PostService {
  constructor(readonly repository: PostRepository, readonly dataSource: DataSource);

  @Transactional() // Will open a transaction if one doesn't already exist
  async createAndGetPost(id, message): Promise<Post> {
    const post = this.repository.create({ id, message });

    await this.repository.save(post);

    return dataSource.createQueryBuilder(Post, 'p').where('id = :id', id).getOne();
  }
}

Data Sources

Multiple DataSources

In new versions of TypeORM the name property in Connection / DataSource is deprecated, so to work conveniently with multiple DataSource the function addTransactionalDataSource allows you to specify custom the name.

@Module({
  imports: [
    TransactionModule.forRoot(),

    // Postgres Database
    TypeOrmModule.forRootAsync({
      useFactory: () => ({
        type: 'postgres',
        host: '127.0.0.1',
        port: 5435,
        username: 'beobwoo',
        password: 'testtest',
        database: 'test_db',
      }),
    }),
    // Postgres Database 2
    TypeOrmModule.forRoot({
      name: 'second-data-source',
      type: 'postgres',
      host: '127.0.0.1',
      port: 5436,
      username: 'beobwoo',
      password: 'testtest',
      database: 'test_db_2',
    }),
  ],
  providers: [],
})
export class AppModule {}

If you don't specify a name, it defaults to default.

Now, you can use this name in API by passing the connectionName property as options to explicitly define which Data Source you want to use:

  @Transactional({ connectionName: 'second-data-source' })
  async fn() { ... }

OR

runInTransaction(
  () => {
    // ...
  },
  { connectionName: 'second-data-source' },
);

Note: If you use TypeORM.forRootAsync when Nest.js uses DataSourceName, you must also enter the name attribute in the body. - Nestjs Docs

Select the name of the data source to participate

If you register using the forRoot method, you need the ability to select from multiple DataSources. If used without additional options, register all DataSources automatically.

TransactionModule.forRoot({
  dataSourceNames: ['default'], // if you want regist Default DataSource only (no name TypeORM dataSource)
}),

Transaction Propagation

The following propagation options can be specified:

  • REQUIRED (default behaviour) - Support a current transaction, create a new one if none exists.
  • SUPPORTS - Support a current transaction, execute non-transactionally if none exists.
  • NESTED - Execute within a nested transaction if a current transaction exists, behave like REQUIRED else.
  • REQUIRES_NEW - If this propagation option is applied, stop the transaction in progress and always start the transaction anew with the new database connection.

Isolation Levels

The following isolation level options can be specified:

  • READ_UNCOMMITTED - A constant indicating that dirty reads, non-repeatable reads and phantom reads can occur.
  • READ_COMMITTED - A constant indicating that dirty reads are prevented; non-repeatable reads and phantom reads can occur.
  • REPEATABLE_READ - A constant indicating that dirty reads and non-repeatable reads are prevented; phantom reads can occur.
  • SERIALIZABLE = A constant indicating that dirty reads, non-repeatable reads and phantom reads are prevented.

Commit, RollBack hooks

The library provides commit, rollback hooks provided by existing typeorm-transactional as functions (hooks API), but additionally provides the ability to register the method to be executed after commit or rollback success in the form of a method decorator in the form of a listener.

import { Injectable } from '@nestjs/common';
import { TransactionEventListener } from 'nestjs-transactional';
import { UserService } from './user.service';

@Injectable()
export class CustomTransactionEventListener implements TransactionEventListener {
  constructor(private readonly userService: UserService) {}

  async onCommit(...param: unknown[]): Promise<void> {
    await this.userService.userRepository.find();
  }

  async onRollBack(e: Error, ...param: unknown[]): Promise<void> {
    await this.userService.userRepository.find();
  }
}

The arguments for both the onCommit and onRollback methods are the same as the arguments for the target method, but for onRollBack, the error object that triggered rollback is additionally handed over to the first argument.

// user.module.ts
@Module({
  ...,
  controllers: [UserController],
  providers: [
    CustomTransactionEventListener,
  ],
})
export class UserModule {}

Transaction commit, rollback defines the functionality that should be executed upon successful implementation of the TransactionEventListener in the form of a provider. It is the same as a typical Nestjs provider and can be implemented by injecting other services within the module.

@Transactional()
@TransactionalEventListeners(CustomTransactionEventListener)
async createUser(...param: unknown[]) {
  ...
}

Test Mocking

Unit Test

@Transactional can be mocked to prevent running any of the transactional code in unit tests.

This can be accomplished in Jest with:

jest.mock('nestjs-transactional', () => ({
  Transactional: () => () => ({}),
}));

Repositories, services, etc. can be mocked as usual.

Integration Test

Note: This feature was Deprecated from 0.1.5^.

import { getTestQueryRunnerToken, TestTransactionModule } from 'nestjs-transaction';

beforeAll(async () => {
  const module = await Test.createTestingModule({
    imports: [
      AppModule,
      TestTransactionModule.forRoot(), // <-- if regist TestTransactionModule, you can use testQueryRunner
    ],
  }).compile();

  app = module.createNestApplication();
  await app.init(); // NOTE : TransactionModule using lifecycle hooks
  dataSource = app.get<DataSource>(getDataSourceToken());

  // A QueryRunner used by @Transactional applied methods
  // while participating in a transaction to execute a query
  testQueryRunner = app.get<QueryRunner>(getTestQueryRunnerToken());
});

beforeEach(async () => {
  await testQueryRunner.startTransaction();
});

afterEach(async () => {
  await testQueryRunner.rollbackTransaction();
});

it('it should be return ResponseDTO Array in body', async () => {
  const response = await request(app.getHttpServer())
    .patch(`/v1/<<url>>`)
    .send(dto)
    .expect(HttpStatus.OK);

  expect(response.body).toStrictEqual([]);
});

When using the above method in integrated tests using jest, each test result is automatically rolled back, eliminating the need to clean the table data after each test. This requires additional recall of TestTransactionModule, as opposed to registering only AppModule in general.

TestQueryRunner is not available unless you have registered a TestTransactionModule. It is of course possible to manually clean the test data without registering the TestTransactionModule.

API

Transaction Options

{
  connectionName?: string;
  isolationLevel?: IsolationLevel;
  propagation?: Propagation;
}
  • connectionName: The name of the DataSource to use for this transactional context. It allows you to specify a specific DataSource if you have multiple DataSources configured in your application. (the data sources)

  • isolationLevel: The isolation level for the transactional context. Isolation levels define the degree to which one transaction must be isolated from the effects of other concurrent transactions. Common isolation levels include READ_COMMITTED, REPEATABLE_READ, and SERIALIZABLE. (isolation levels)

  • propagation: The propagation behavior for nest transactional contexts. Propagation determines how transactions should be propagated from one method to another. Common propagation behaviors include REQUIRED, REQUIRES_NEW, and NESTED. (propagation behaviors)

runOnTransactionCommit

The runOnTransactionCommit function is part of the nestjs-transactional package. It is used within the context of a transactional operation in TypeORM. This function allows you to specify a callback function that will be executed when the transaction is successfully committed.

When you perform a transactional operation, such as inserting, updating, or deleting data in a database, you want to ensure that the changes are applied atomically. This means that either all the changes are committed successfully, or none of them are applied at all. The runOnTransactionCommit function provides a way to execute additional logic or actions after the transaction is successfully committed.

To use runOnTransactionCommit, you need to pass a callback function as an argument. This callback function will be invoked only if the transaction is committed successfully. You can use this callback function to perform any additional tasks or actions that should be executed after the transaction is completed.

Here's an example of how you can use runOnTransactionCommit:

import { runOnTransactionCommit } from 'nestjs-transactional';

@Injectable()
export class UserService {
    ...
    @Transactional()
    async createUser(id?: string) {
      const user = User.create({ id });

      await this.dataSource.manager.save(user);

      runOnTransactionCommit(async () => {
        await this.logService.saveSuccessLog(...);
        console.log('User Save Success.');
      });
    }
}

runOnTransactionRollBack

The runOnTransactionRollback function is part of the typeorm package. It is used within the context of a transactional operation in TypeORM. This function allows you to specify a callback function that will be executed when the transaction is rolled back.

A transaction is rolled back when an error occurs during the transactional operation. This means that all changes made during the transaction are undone, and the state of the database is reverted to what it was before the transaction started. The runOnTransactionRollback function provides a way to execute additional logic or actions after the transaction is rolled back.

To use runOnTransactionRollback, you need to pass a callback function as an argument. This callback function will be invoked only if the transaction is rolled back. You can use this callback function to perform any additional tasks or actions that should be executed after the transaction is rolled back.

Here's an example of how you can use runOnTransactionRollback:

import { runOnTransactionRollback } from 'nestjs-transactional';

@Injectable()
export class UserService {
    ...
    @Transactional()
    async createUser(id?: string) {
      const user = User.create({ id });

      await this.dataSource.manager.save(user);

      runOnTransactionRollback(async (e: unknown) => {
        console.log('User Save Failed.', e);
        await this.logService.saveFailLog(...);
      });
    }
}

addTransactionalDataSource(input): DataSource

Add TypeORM DataSource to transactional context.

addTransactionalDataSource(new DataSource(...));
addTransactionalDataSource({ name: 'default', : new DataSource(...) });