@tsrt/typeorm-transactions
TypeScript icon, indicating that this package has built-in type declarations

0.10.1 • Public • Published

TsRT: TypeORM Transactions

npm version GitHub license Size Downloads

Convenient transactions support for great TypeORM.

The most basic example:

Refer to full example for more info

import { Transaction } from  '@tsrt/typeorm-transactions';

async function makeStuffInTransaction(): Promise<any> {
  const t =  new Transaction({ ... });
  await t.begin();

  try {
    // await transaction.manager.createQueryBuilder()...
    // Or someRepository.create(...)
    await t.commit();
  } catch (err) {
    await t.rollback(err);
  }
}

Preconditions

  1. CLS Namespace should be initialized before transactions usage.
  2. Everything should be wrapped into created CLS Namespace.
  3. Your repositories should (one of):
    • extend BaseRepository from @tsrt/typeorm-transactions (annotated w/ TypeORM's EntityRepository).
    • extend TypeORM's Repository (annotated w/ TypeORM's EntityRepository) and before usage patchTypeOrmRepository should be called.
    • any class w/ manager (TypeORM's EntityManager) property and before usage patchTypeOrmRepository should be called.
    • any class w/ TypeORM's EntityManager as first argument in constructor and before usage patchTypeOrmRepository should be called (or extend BaseRepository).

Init CLS Namespace

// index.ts
import { createTransactionsNamespace, patchTypeOrmRepository } from '@tsrt/typeorm-transactions';

createTransactionsNamespace(); // This one should be called before any transactions usage
patchTypeOrmRepository(); // This one is only necessary if repositories extends native TypeORM's Repository.

Wrap into CLS Namespace

import { bindTransactionsNamespace, execInTransactionsNamespace } from '@tsrt/typeorm-transactions';

// Wrap only function:
execInTransactionsNamespace(/* Any async code which uses transactions via CLS from this package */)

// Example for Express:
const app = express();
app.use(bindTransactionsNamespace);

// If using only `Transactional` decorator - no need to init namespace first.
class Service {
  @Transactional()
  public async doStuff(): Promise<any> {
    // do stuff inside transaction
  }
}

!!! Note, that popular express-session package can lead to context loss. To prevent that try (one of):

  1. bind namespace to express AFTER calling express-session middleware.
  2. call express-session middleware and bind next function to namespace.

Example:

import express from 'express';
import expressSession from 'express-session';
import { createTransactionsNamespace, bindTransactionsNamespace } from '@tsrt/typeorm-transactions';;

const ns = createTransactionsNamespace();
const app = express();

// 1. bind namespace to express _AFTER_ calling `express-session` middleware.
app.use(expressSession({ ... }));
app.use(bindTransactionsNamespace);


// 2. call `express-session` middleware and bind `next` function to namespace.
app.use((req, res, next) => expressSession({ ... })(req, res, ns.bind(next)));

Repositories

1. Extending BaseRepository from @tsrt/typeorm-transactions:
// repository.ts
import { BaseRepository } from '@tsrt/typeorm-transactions';
import { EntityRepository } from 'typeorm';

EntityRepository(SomeEntity)
export class SomeBaseRepository extends BaseRepository { /* ... */ }


// somewhereElse.ts
import { getConnection } from 'typeorm';
import { SomeBaseRepository } from 'path/to/repository.ts';

getConnection(/* ... */).getCustomRepoistory(SomeBaseRepository).create(/* ... */);
2. Extending TypeORM's native Repository and patching it w/ patchTypeOrmRepository:
// index.ts
import { patchTypeOrmRepository } from '@tsrt/typeorm-transactions';

patchTypeOrmRepository(); // This on shoud be called somewhere just after `createTransactionsNamespace()`.


// repository.ts
import { Repository, EntityRepository } from 'typeorm';

EntityRepository(SecondEntity)
export class SomeRepository extends Repository { /* ... */ }


// somewhereElse.ts
import { getConnection } from 'typeorm';
import { SomeRepository } from 'path/to/repository.ts';

getConnection(/* ... */).getCustomRepoistory(SomeRepository).create(/* ... */);
3. Creating any repository from scratch and call patchTypeOrmRepository:
// repository.ts
import { patchTypeOrmRepository } from '@tsrt/typeorm-transactions';
import { Repository, EntityManager } from 'typeorm';

export class SomeRepository {
  public readonly manager: EntityManager;
  /* ... */
}
patchTypeOrmRepository(SomeRepository, { /* ... connection options ... */ });

// somewhereElse.ts
import { SomeRepository } from 'path/to/repository.ts';

new SomeRepository().create(/* ... */);
4. Creating any repository w/ EntityManager as first constructor argument:
import { patchTypeOrmRepository } from '@tsrt/typeorm-transactions';
import { Repository, EntityManager, getManager() } from 'typeorm';

export class SomeRepository {
  constructor(public readonly manager: EntityManager;) {}
  /* ... */
}

// Then
// 1. patchTypeOrmRepository(SomeRepository, { /* ... connection options ... */ });
// 2. Or extend BaseRepository
// 3. Also could be annotated w/ TypeORM's `EntityRepository`.


// somewhereElse.ts
import { SomeRepository } from 'path/to/repository.ts';

new SomeRepository(getManager()).create(/* ... */);

Propagation

  • REQUIRED (default) - supports existing transaction if it is or creates new if there is no.
  • SEPARATE - creates new transaction even if there is already an existing transaction.
  • SUPPORT - supports only existing transaction and does not create new.

APIs

  1. Transaction - constructor to create new single Transaction.
  2. TransactionManager - constructor to create TransactionManager for specific connection w/ default options. Has 3 methods:
    • createTransaction - creates new single Transaction w/ TransactionManager default options and connection.
    • transaction - creates and starts new single Transaction. Could be provided w/ callback to execute inside transaction which will rollback automatically if no manual commit is called. If no callback provided, commit and rollback should be done manually.
    • autoTransaction - same as transaction method (with callback case), but will commit automatically if no error thrown.
  3. Transactional - method decorator (more convenient/declarative, but less flexible comparing to TransactionManager). Has 2 variants:
    • createTransactional - factory function for creating Transactional decorator for specific connection w/ default options. (uses TransactionManager under the hood).
    • Transactional - decorator itself w/ ability to provide any connection and other options.

Full example

import { PrimaryGeneratedColumn, Column, Entity, Repository, getConnection, EntityRepository } from 'typeorm';
import { createTransactionsNamespace, bindTransactionsNamespace, patchTypeOrmRepository, TransactionManager } from '@tsrt/typeorm-transactions';

createTransactionsNamespace();
patchTypeOrmRepository();

const tm = new TransactionManager();

const app = express();
app.use(bindTransactionsNamespace);

@Entity('Users')
class User {
  @PrimaryGeneratedColumn()
  public id: number;

  @Column()
  public name: string;
}

@EntityRepository(User)
class UsersRepository extends Repository {
  public async createUser(body: IUserPayload): Promise<User> {
    const user = this.manager.create(body);
    return this.manager.save(User, user);
  }
}

class UsersService {
  public async createUsers(users: IUserPayload[]): Promise<User[]> {
    return tm.autoTransaction(async () => {
      const repository = getConnection().getCustomRepository(UsersRepository);
      const promises = users.map((item) => repository.createUser(item));
      return Promise.all(promises);
    });
  }
}

app.use('/test', async (req, res) => {
  const users = await new UsersService().createUsers([
    { name: 'First User' },
    { name: 'Second User' },
  ]);
  res.status(200).send(users);
});

app.listen(3333);

Legacy API

Before 0.8.0 there was ability to use Transactions without cls-hooked.. Still old API is available for usage under @tsrt/typeorm-transactions/dist/legacy.

Most basic usage is same as in this example, w/ only one difference - it is not necessary to initialise and use clsNamespace.

Legacy API instantiates separate repositories for each transaction.

For more docs please refer to legacy docs.

License

This project is licensed under the terms of the MIT license.

Package Sidebar

Install

npm i @tsrt/typeorm-transactions

Weekly Downloads

94

Version

0.10.1

License

MIT

Unpacked Size

82.7 kB

Total Files

30

Last publish

Collaborators

  • mopc