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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@profundium/typeorm-transactional

v1.0.1

Published

A transactional method decorator for TypeORM that propagates transactions between repositories and service methods using AsyncLocalStorage.

Readme

@profundium/typeorm-transactional

npm version

Maintained continuation

This package is maintained at profundium/typeorm-transactional. It continues Aliheym/typeorm-transactional.

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

See Changelog

Installation

## npm
npm install --save @profundium/typeorm-transactional

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

Or

yarn add @profundium/typeorm-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

Compatibility

This library supports TypeORM 0.3.x and 1.x.

This library requires Node.js >=24.11.0 and uses the built-in AsyncLocalStorage. Development and CI are pinned to Node.js v24.18.0. NestJS applications using TypeORM 1.x also need @nestjs/typeorm >=11.0.1.

Initialization

In order to use it, you will first need to initialize the transactional context before your application is started

import { initializeTransactionalContext } from '@profundium/typeorm-transactional';

initializeTransactionalContext();
...
app = express()
...

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:

import { DataSource } from 'typeorm';
import { initializeTransactionalContext, addTransactionalDataSource } from '@profundium/typeorm-transactional';
...
const dataSource = new DataSource({
	  type: 'postgres',
    host: 'localhost',
    port: 5435,
    username: 'postgres',
    password: 'postgres'
});
...

initializeTransactionalContext();
addTransactionalDataSource(dataSource);

...

Example for Nest.js:

// main.ts

import { NestFactory } from '@nestjs/core';
import { initializeTransactionalContext } from '@profundium/typeorm-transactional';

import { AppModule } from './app';

const bootstrap = async () => {
  initializeTransactionalContext();

  const app = await NestFactory.create(AppModule, {
    abortOnError: true,
  });

  await app.listen(3000);
};

bootstrap();
// app.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { addTransactionalDataSource } from '@profundium/typeorm-transactional';

@Module({
	imports: [
	   TypeOrmModule.forRootAsync({
	     useFactory() {
	       return {
	         type: 'postgres',
	         host: 'localhost',
	         port: 5435,
	         username: 'postgres',
	         password: 'postgres',
	         synchronize: true,
	         logging: false,
	       };
	     },
	     async dataSourceFactory(options) {
	       if (!options) {
	         throw new Error('Invalid options passed');
	       }

	       return addTransactionalDataSource(new DataSource(options));
	     },
	   }),

	   ...
	 ],
	 providers: [...],
	 exports: [...],
})
class AppModule {}

You do not need to use a custom base repository or otherwise redefine repositories.

You can also use this library with custom TypeORM repositories. You can read more about them here and here.

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:

  • 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.

Nested savepoints

Propagation.NESTED is the explicit opt-in for savepoint behavior. When an outer transaction exists, the nested callback uses the same TypeORM QueryRunner: success releases its savepoint, while failure rolls back only to that savepoint. Without an outer transaction, NESTED behaves like REQUIRED and starts a transaction.

await runInTransaction(async () => {
  await usersRepository.save(outerUser);

  try {
    await runInTransaction(() => usersRepository.save(optionalUser), {
      propagation: Propagation.NESTED,
    });
  } catch {
    // optionalUser was rolled back; outerUser can still be committed
  }
});

This differs from REQUIRES_NEW, which opens an independent transaction whose commit can survive an outer rollback. Hooks registered in a successful NESTED scope wait for the outer transaction's final commit or rollback; hooks in a failed scope receive that savepoint rollback once.

TypeORM controls native savepoint names (for example, typeorm_1). Its public QueryRunner API does not expose portable custom naming, so this library does not provide a savepoint-name callback. Run nested scopes sequentially: parallel sibling NESTED calls on one transaction reject with TransactionalError before TypeORM's savepoint depth can be corrupted.

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 successfully 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 successfully committed
  • runOnTransactionRollback(cb) takes a callback to be executed after the current transaction rolls back. The callback gets the error that initiated the rollback as a parameter.
  • runOnTransactionComplete(cb) takes a callback to be executed at the completion of the current transactional context. If there was an error, it gets passed as an argument.
export class PostService {
    constructor(readonly repository: PostRepository, 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;
    }
}

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('@profundium/typeorm-transactional', () => ({
  Transactional: () => () => ({}),
}));

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

API

Library Options

{
  maxHookHandlers?: number
}
  • maxHookHandlers - Controls how many hooks (commit, rollback, complete) can be used simultaneously. If you exceed the number of hooks of same type, you get a warning. This is a useful to find possible memory leaks. You can set this options to 0 or Infinity to indicate an unlimited number of listeners. By default, it's 10.

Transaction Options

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

initializeTransactionalContext(options): void

Initialize transactional context.

initializeTransactionalContext(options?: TypeormTransactionalOptions);

Optionally, you can set some options.

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();

...

runOnTransactionCommit(cb: Callback): void

Takes a callback to be executed after the current transaction was successfully committed

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

runOnTransactionRollback(cb: Callback): void

Takes a callback to be executed after the current transaction rolls back. The callback gets the error that initiated the rollback as a parameter.

  @Transactional()
  async createPost(id, message): Promise<Post> {
      const post = this.repository.create({ id, message });
      const result = await this.repository.save(post);

      runOnTransactionRollback((e) => this.events.emit(e));

      return result;
  }

runOnTransactionComplete(cb: Callback): void

Takes a callback to be executed at the completion of the current transactional context. If there was an error, it gets passed as an argument.

  @Transactional()
  async createPost(id, message): Promise<Post> {
      const post = this.repository.create({ id, message });
      const result = await this.repository.save(post);

      runOnTransactionComplete((e) => this.events.emit(e ? e : 'post created'));

      return result;
  }