Giter Site home page Giter Site logo

labibramadhan / typeorm-transactional-cls-hooked Goto Github PK

View Code? Open in Web Editor NEW

This project forked from odavid/typeorm-transactional-cls-hooked

0.0 2.0 0.0 70 KB

A Transactional Method Decorator for typeorm that uses cls-hooked to handle and propagate transactions between different repositories and service methods. Inpired by Spring Trasnactional Annotation and Sequelize CLS

License: MIT License

TypeScript 100.00%

typeorm-transactional-cls-hooked's Introduction

typeorm-transactional-cls-hooked

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

Inspired by Spring Transactional Annotation and Sequelize CLS

See Changelog

Installation

yarn add typeorm-transactional-cls-hooked
## Needed dependencies
yarn add cls-hooked typeorm reflect-metadata

Or

npm install --save typeorm-transactional-cls-hooked
## Needed dependencies
npm install --save cls-hooked 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

Initialization

In order to use it, you will first need to initialize the cls-hooked namespace before your application is started

import { initializeTransactionalContext } from 'typeorm-transactional-cls-hooked';

initializeTransactionalContext() // Initialize cls-hooked
...
app = express()
...

BaseRepository

Since this is an external library, all your typeorm repositories will need to be a custom repository extending the BaseRepository class.

// Post.entity.ts
@Entity()
export class Post{
  @PrimaryGeneratedColumn()
  id: number

  @Column
  message: string
  ...
}

// Post.repository.ts
import { EntityRepository } from 'typeorm';
import { BaseRepository } from 'typeorm-transactional-cls-hooked';

@EntityRepository(Post)
export class PostRepository extends BaseRepository<Post> {}

The only purpose of the BaseRepository class is to make sure the manager property of the repository will always be the right one. In cases inheritance is not possible, you can always use the same code from BaseRepository within your own repository code.

import { getEntityManagerOrTransactionManager } from 'typeorm-transactional-cls-hooked';

class MyRepository<Entity extends ObjectLiteral> extends Repository<Entity> {
  private _connectionName: string = 'default'
  private _manager: EntityManager | undefined

  set manager(manager: EntityManager) {
    this._manager = manager
    this._connectionName = manager.connection.name
  }

  // Always get the entityManager from the cls namespace if active, otherwise, use the original or getManager(connectionName)
  get manager(): EntityManager {
    return getEntityManagerOrTransactionManager(this._connectionName, this._manager)
  }
}

Patching TypeORM Repository with BaseRepository

Sometimes there is a need to keep using the TypeORM Repository instead of using the BaseRepository. For this cases, you will need to "mixin/patch" the original Repository with the BaseRepository. By doing so, you will be able to use the original Repository and not change the code or use BaseRepository.

This method was taken from https://gist.github.com/Diluka/87efbd9169cae96a012a43d1e5695667 (Thanks @Diluka)

In order to do that, the following should be done during initialization:

import { initializeTransactionalContext, patchTypeORMRepositoryWithBaseRepository } from 'typeorm-transactional-cls-hooked';

initializeTransactionalContext() // Initialize cls-hooked
patchTypeORMRepositoryWithBaseRepository() // patch Repository with BaseRepository.

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)
  • 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: PostRepsitory)

  @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)
  }
}

Transaction Propagation

The following propagation options can be specified:

  • MANDATORY - Support a current transaction, throw an exception if none exists.
  • NESTED - Execute within a nested transaction if a current transaction exists, behave like REQUIRED else.
  • NEVER - Execute non-transactionally, throw an exception if a transaction exists.
  • NOT_SUPPORTED - Execute non-transactionally, suspend the current transaction if one exists.
  • REQUIRED (default behaviour) - Support a current transaction, create a new one if none exists.
  • REQUIRES_NEW - Create a new transaction, and suspend the current transaction if one 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.

NOTE: If a transaction already exist and a method is decorated with @Transactional and propagation does not equal to REQUIRES_NEW, then the declared isolationLevel value will not be taken into account.

Hooks

Because you hand over control of the transaction creation to this library, there is no way for you to know whether or not the current transaction was sucessfully persisted to the database.

To circumvent that, we expose three helper methods that allow you to hook into the transaction lifecycle and take appropriate action after a commit/rollback.

  • runOnTransactionCommit(cb) takes a callback to be executed after the current transaction was sucessfully committed
  • runOnTransactionRollback(cb) takes a callback to be executed after the current transaction rolls back. The callback gets the error that initiated the roolback as a parameter.
  • runOnTransactionEnd(cb) takes a callback to be executed at the end of the current transactional context. If there was an error, it gets passed as an argument.
export class PostService {
    constructor(readonly repository: PostRepsitory, readonly events EventService)

        @Transactional()
        async createPost(id, message): Promise<Post> {
            const post = this.repository.create({ id, message })
            const result = await this.repository.save(post)
            runOnTransactionCommit(() => this.events.emit('post created'))
            return result
        }
}

typeorm-transactional-cls-hooked's People

Contributors

ashleyw avatar odavid avatar svvac avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.