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

@tsrt/typeorm-transactions

v0.10.1

Published

TypeORM convenient transactions management

Downloads

499

Readme

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.