JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 88
  • Score
    100M100P100Q69946F
  • License MIT

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

Package Exports

  • nestjs-transaction
  • nestjs-transaction/dist/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (nestjs-transaction) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

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.


Installation

## npm
npm install --save nestjs-transactional

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

Or

yarn add nestjs-transactional

## 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

IMPORTANT NOTE

Calling initializeTransactionalContext must happen BEFORE any application context is initialized!


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.

To be able to use TypeORM entities in transactions, you must first add a DataSource using the addTransactionalDataSource function:

Example for Nest.js:

// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { addTransactionalDataSource } from 'nestjs-transactional;
import { mainDataSourceOpiton, subDataSourceOption } from './data-sources';

@Module({
  imports: [
    // Postgres Database
    TypeOrmModule.forRootAsync({
      useFactory: () => mainDataSourceOpiton,
      // dataSource receives the configured DataSourceOptions
      // and returns a Promise<DataSource>.
      dataSourceFactory: async (options) => {
        if (!options) {
          throw new Error('Invalild DataSource options');
        }

        return addTransactionalDataSource(new DataSource(options));
      },
    }),
    // Postgres Database 2
    TypeOrmModule.forRootAsync({
      name: LOG_DB_NAME,
      useFactory: () => subDataSourceOption,
      dataSourceFactory: async (options) => {
        if (!options) {
          throw new Error('Invalild DataSource options');
        }

        return addTransactionalDataSource(new DataSource(options));
      },
    }),
  ],
  providers: [],
})
export class DatabaseModule {}

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

NOTE: You can add multiple 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

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:

addTransactionalDataSource({
    name: 'second-data-source',
    dataSource: new DataSource(...)
});

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' },
);

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.

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.

Unit Test Mocking

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

This can be accomplished in Jest with:

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

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


API

Transaction Options

{
  connectionName?: string;
  isolationLevel?: IsolationLevel;
  propagation?: Propagation;
}

addTransactionalDataSource(input): DataSource

Add TypeORM DataSource to transactional context.

addTransactionalDataSource(new DataSource(...));

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

runInTransaction(fn: Callback, options?: Options): Promise<...>

Run code in transactional context.

...

runInTransaction(() => {
    ...

    const user = this.usersRepo.update({ id: 1000 }, { state: action });

    ...
}, { propagation: Propagation.REQUIRES_NEW });

...

wrapInTransaction(fn: Callback, options?: Options): WrappedFunction

Wrap function in transactional context

...

const updateUser = wrapInTransaction(() => {
    ...

    const user = this.usersRepo.update({ id: 1000 }, { state: action });

    ...
}, { propagation: Propagation.NEVER });

...

await updateUser();