typeorm-transactional

repository·master·Indexed 42 days ago

https://github.com/aliheym/typeorm-transactional

A utility library providing a @Transactional method decorator for TypeORM. It uses AsyncLocalStorage or cls-hooked to automatically propagate transaction contexts across repositories and service methods, inspired by Spring Transactional and Sequelize CLS. Version 0.5.0 supports configurable propagation behaviors (e.g., REQUIRED, REQUIRES_NEW), isolation levels, and lifecycle hooks like runOnTransactionCommit.

Tokens
7.5K
Snippets
29
Records
33
Agent score
53%

What's inside typeorm-transactional

  1. Configure Transaction Propagation

    master

    The propagation option in the @Transactional decorator defines how the method behaves when a transaction is already active:

    • REQUIRED (default): 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.
    • 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.
    • SUPPORTS: Support a current transaction, execute non-transactionally if none exists.
    • NOT_SUPPORTED: Execute non-transactionally, suspend the current transaction if one exists.
    • NEVER: Execute non-transactionally, throw an exception if a transaction exists.
  2. Select a Storage Driver

    master

    The storageDriver option determines how the library handles and propagates transaction context. Use the StorageDriver enum to select one of the following:

    • AUTO: Automatically selects the best mechanism based on Node.js version (AsyncLocalStorage for Node 16+, cls-hooked for older versions).
    • CLS_HOOKED: Uses the cls-hooked package. Supports legacy Node.js versions via AsyncWrap or async_hooks for newer versions.
    • ASYNC_LOCAL_STORAGE: Uses the built-in AsyncLocalStorage (requires Node.js 16+).
    import { StorageDriver, initializeTransactionalContext } from 'typeorm-transactional';
    
    initializeTransactionalContext({ storageDriver: StorageDriver.AUTO });
  3. Configure Transaction Isolation Levels

    master

    You can specify the database isolation level via the @Transactional decorator.

    Important: If a transaction already exists and the propagation is NOT set to REQUIRES_NEW, the declared isolationLevel will be ignored.

    Available levels:

    • READ_UNCOMMITTED
    • READ_COMMITTED
    • REPEATABLE_READ
    • SERIALIZABLE
  4. Register a TypeORM DataSource

    master

    To enable transactions with your TypeORM entities, you must register your DataSource using addTransactionalDataSource. This allows the library to manage the transaction lifecycle for that specific data source.

    If you have multiple data sources, you can provide a name to identify them uniquely.

    import { DataSource } from 'typeorm';
    import { addTransactionalDataSource } from 'typeorm-transactional';
    
    const dataSource = new DataSource({
      type: 'postgres',
      host: 'localhost',
      // ... other config
    });
    
    addTransactionalDataSource(dataSource);
  5. Install typeorm-transactional and dependencies

    master

    Install the core package along with its required dependencies typeorm and reflect-metadata.

    Note: You must import reflect-metadata globally in your application for TypeORM to function correctly.

    ## npm
    npm install --save typeorm-transactional
    npm install --save typeorm reflect-metadata
    
    ## yarn
    yarn add typeorm-transactional
    yarn add typeorm reflect-metadata
  6. Initialize the transactional context

    master

    Before starting your application context, you must initialize the transactional context. This is typically done at the very beginning of your entry point (e.g., main.ts or index.ts).

    Use initializeTransactionalContext with a storageDriver. Setting it to StorageDriver.AUTO is recommended to let the library choose between AsyncLocalStorage (ALS) or cls-hooked.

    import { initializeTransactionalContext, StorageDriver } from 'typeorm-transactional';
    
    initializeTransactionalContext({ storageDriver: StorageDriver.AUTO });
    // ... initialize your app (Express, NestJS, etc.) after this call
  7. Initialize the transactional context

    master

    Before using the library, you must call initializeTransactionalContext to set up the storage driver and patch TypeORM components (like EntityManager and Repository) to support transactional propagation. You can optionally provide configuration options to customize the library's behavior.

    Supported options:

    • maxHookHandlers (number): Controls how many hooks (commit, rollback, complete) can be used simultaneously. Default is 10. Setting this to 0 or Infinity allows unlimited listeners.
    • storageDriver (StorageDriverEnum): Controls the storage driver used for persistence during async request timespans. By default, AsyncLocalStorage is preferred if supported by the runtime.
    import { initializeTransactionalContext } from 'typeorm-transactional';
    
    initializeTransactionalContext({
      maxHookHandlers: 20,
      // storageDriver: StorageDriverEnum.ASYNC_LOCAL_STORAGE
    });
  8. Run code in a transaction with runInTransaction

    master

    Use runInTransaction to execute a callback function within a transactional context. It accepts an optional Options object to control connection, isolation, and propagation.

    import { runInTransaction, Propagation } from 'typeorm-transactional';
    
    await runInTransaction(async () => {
      const user = await this.usersRepo.update({ id: 1000 }, { state: 'active' });
      // ... other operations
    }, { propagation: Propagation.REQUIRES_NEW });
  9. Execute callbacks on transaction lifecycle events

    master

    You can register callbacks to run at specific stages of a transaction's lifecycle:

    • runOnTransactionCommit(cb): Executes cb after the transaction is successfully committed.
    • runOnTransactionRollback(cb): Executes cb after a rollback occurs. The callback receives the error that triggered the rollback as an argument.
    • runOnTransactionComplete(cb): Executes cb when the transactional context is finished (either commit or rollback). The callback receives the error (if any) as an argument.
    import { 
      Transactional, 
      runOnTransactionCommit, 
      runOnTransactionRollback, 
      runOnTransactionComplete 
    } from 'typeorm-transactional';
    
    class PostService {
      @Transactional()
      async createPost(id: string, message: string) {
        const post = this.repository.create({ id, message });
        const result = await this.repository.save(post);
    
        // Runs after successful commit
        runOnTransactionCommit(() => this.events.emit('post created'));
    
        // Runs after rollback, receives error
        runOnTransactionRollback((err) => this.events.emit('post failed', err));
    
        // Runs at the very end, receives error or null
        runOnTransactionComplete((err) => this.events.emit(err ? 'error' : 'success'));
    
        return result;
      }
    }
  10. Use the @Transactional decorator

    master

    Apply the @Transactional() decorator to service methods to ensure they run within a transaction.

    • If a transaction already exists, the method will join it (default behavior).
    • You can specify a connectionName to target a specific DataSource.
    • You can specify propagation to control how the transaction behaves relative to existing ones.
    • You can specify isolationLevel to set the database isolation level.
    import { Transactional } from 'typeorm-transactional';
    
    export class PostService {
      constructor(readonly repository: PostRepository) 
    
      @Transactional() // Joins existing transaction or creates a new one
      async createPost(id, message): Promise<Post> {
        const post = this.repository.create({ id, message });
        return this.repository.save(post);
      }
    
      @Transactional({ connectionName: 'second-data-source' })
      async useOtherDataSource() {
        // ...
      }
    }
  11. Use Transaction Lifecycle Hooks

    master

    Since the library manages the transaction lifecycle, use these helper functions to execute logic based on the transaction outcome:

    • runOnTransactionCommit(cb): Executes cb after the transaction is successfully committed.
    • runOnTransactionRollback(cb): Executes cb after a rollback. The callback receives the error that caused the rollback.
    • runOnTransactionComplete(cb): Executes cb when the transactional context completes. If an error occurred, it is passed to the callback.
    import { Transactional, runOnTransactionCommit } from 'typeorm-transactional';
    
    export class PostService {
        @Transactional()
        async createPost(id, message): Promise<Post> {
            const result = await this.repository.save(this.repository.create({ id, message }));
    
            runOnTransactionCommit(() => {
                console.log('Post was successfully saved to the database!');
            });
    
            return result;
        }
    }