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

nest-transact

v9.1.2

Published

This package give you a simplest possibility to use transactions with Nestjs

Downloads

1,298

Readme

npm GitHub package.json version npm npm

Current supported versions of Nest.js, Typeorm and other packages

The major version of this package will always match the major version of Nest.js, so if you see nest-transact v9.x.x it means that this version of the package is compatible with Nest.js ^9.x.x.

npm peer dependency version npm peer dependency version npm peer dependency version npm peer dependency version npm peer dependency version

Description

This package takes the use of transactions in Nest.js with TypeORM to the next level. The easiest and fastest way to keep your data intact.

Changelog

Since v9.0.0 of @nestjs/typeorm and v0.3.0 of typeorm you can not create custom repositories with class style, like it showed below:

/**
 * Unfortunatelly, this is a deprecated method of creation of custom repositories, since typeorm ^0.3.0 was released
 * For additional information see: https://github.com/typeorm/typeorm/pull/8616
 * and https://typeorm.io/changelog under the 0.3.0 version changelog (search by "Old ways of custom repository creation were dropped.")
 */
@EntityRepository(Purse)
export class PurseRepository extends Repository<Purse> {
  async findPurseById(purseId: number): Promise<Maybe<Purse>> {
    return await this.findOne({ where: { id: purseId } });
  }
}

Because of that your custom repositories, created in that way will be broken, if you update typeorm or will use @nestjs/typeorm v9.0.0 or newer. Also, that means that those repositories can't be used in transactions too.

The modern way to create a custom repo, by opinion of typeorm-developers should look like this:

/**
 * This is a new way (from typeorm creators -_-) for creation of custom repositories, which might be helpful
 */
export const NewPurseRepository = new DataSource(Config.typeOrmConfig as DataSourceOptions).getRepository(Purse).extend({
  async findPurseById(purseId: number): Promise<Maybe<Purse>> {
    return await this.findOne({ where: { id: purseId } });
  },
});

and here is an official example:

export const UserRepository = myDataSource.getRepository(UserEntity).extend({
  findUsersWithPhotos() {
    return this.find({
      relations: {
        photos: true,
      },
    });
  },
});

If you don't agree with that decision, please write your comment here.

Example

Controller

// ./example/src/transfer/transfer.controller.ts

import { Body, Controller, Post } from '@nestjs/common';
import { ApiResponse, ApiTags } from '@nestjs/swagger';
import { TransferOperationResultDto } from './dto/transfer-operation-result.dto';
import { TransferParamsDTO } from './dto/transfer-params.dto';
import { TransferService } from './transfer.service';
import { DataSource } from 'typeorm';

@Controller('transfer')
@ApiTags('transfer')
export class TransferController {
  constructor(
    private readonly transferService: TransferService,
    /**
     * DataSource instance needed to us to start a transaction
     */
    private readonly dataSource: DataSource,
  ) {
  }

  @Post()
  @ApiResponse({
    type: TransferOperationResultDto,
  })
  async makeRemittanceWithTransaction(@Body() dto: TransferParamsDTO) {
    return this.dataSource.transaction(manager => {
      /**
       * [withTransaction] - is the new method, which provided by [TransactionFor<T>] class
       * and its allow us to start transaction with [transferService] and all its dependencies
       */
      return this.transferService.withTransaction(manager).makeTransfer(
        dto.userIdFrom,
        dto.userIdTo,
        dto.sum,
        dto.withError,
      );
    });
  }
  
  @Post('without-transaction')
  @ApiResponse({
    type: TransferOperationResultDto,
  })
  async makeRemittanceWithoutTransaction(@Body() dto: TransferParamsDTO) {
    return this.transferService.makeTransfer(
      dto.userIdFrom,
      dto.userIdTo,
      dto.sum,
      dto.withError,
    );
  }
}

Services

All the services shown below will make database calls within a single transaction initiated by a controller method launched in a transaction - transferController.makeRemittanceWithTransaction.

// ./example/src/transfer/transfer.service.ts

import { HttpException, Injectable } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { TransactionFor } from 'nest-transact';
import { isNotDefined } from '../tools';
import { constants } from 'http2';
import { TransferOperationResultDto } from './dto/transfer-operation-result.dto';
import { UserService } from '../user/user.service';
import { PurseService } from './purse.service';

@Injectable()
export class TransferService extends TransactionFor<TransferService> {
  constructor(
    /**
     * All these services will be recreated and be a participants of the same transaction
     */
    private readonly purseService: PurseService,
    private readonly userService: UserService,
    /**
     * This is the needed thing for [TransactionFor<T>] logic working
     */
    moduleRef: ModuleRef,
  ) {
    super(moduleRef);
  }

  async makeTransfer(fromId: number, toId: number, sum: number, withError = false): Promise<TransferOperationResultDto> {
    if (fromId === toId) {
      throw new HttpException('USERS MUST HAVE DIFFERENT IDS', 400);
    }
    const fromUser = await this.userService.findUserById(fromId);
    const toUser = await this.userService.findUserById(toId);
    if (isNotDefined(fromUser.defaultPurseId)) {
      throw new HttpException(`USER "${fromId}" DON NOT HAVE A DEFAULT PURSE`, 500);
    }
    if (isNotDefined(toUser.defaultPurseId)) {
      throw new HttpException(`USER "${toId}" DON NOT HAVE A DEFAULT PURSE`, 500);
    }
    const fromPurse = await this.purseService.findPurseById(fromUser.defaultPurseId);
    const toPurse = await this.purseService.findPurseById(toUser.defaultPurseId);
    const modalSum = Math.abs(sum);
    if (fromPurse.balance < modalSum) {
      throw new HttpException(`USER "${fromId}" NOT ENOUGH MONEY: "${fromPurse.balance} < ${modalSum}"`, 400);
    }
    fromPurse.balance -= sum;
    toPurse.balance += sum;
    await this.purseService.savePurse(fromPurse);
    if (withError) {
      throw new HttpException('UNEXPECTED ERROR WAS THROWN WHILE TRANSFER WAS IN PROGRESS', constants.HTTP_STATUS_TEAPOT);
    }
    await this.purseService.savePurse(toPurse);
    return new TransferOperationResultDto({
      sum,
      fromId,
      toId,
      fromBalance: fromPurse.balance,
      toBalance: toPurse.balance,
    });
  }
}
// ./example/src/transfer/purse.service.ts

import { HttpException, Injectable } from '@nestjs/common';
import { isNotDefined } from '../tools';
import { Purse } from './model/purse.model';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';

@Injectable()
export class PurseService {
  constructor(
    /**
     * For now this is the only way to inject repositories into services without additional complex logic
     * which needed for the new custom repository api from the typeorm creators
     */
    @InjectRepository(Purse)
    private readonly purePurseRepository: Repository<Purse>,
  ) {
  }

  async findPurseById(purseId: number): Promise<Purse> {
    const purse = await this.purePurseRepository.findOne({ where: { id: purseId } });
    if (isNotDefined(purse)) {
      throw new HttpException(`NOT FOUND PURSE WITH ID "${purseId}"`, 404);
    }
    return purse;
  }

  async savePurse(purse: Purse): Promise<Purse> {
    return this.purePurseRepository.save(purse);
  }
}

Excluding something from the recreation flow

Also, you can pass some class-types into the .withTransaction(manager, { excluded: [...yourClasses] }) method to exclude them (and its dependencies) from the recreation flow. It can be, for example, some cache services, or anything, which you don't want to recreate and which is not a part of transaction logic:

// ./example/src/transfer/info.controller.ts

import { Controller, Get } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { InfoService } from './info.service';
import { CacheService } from './cache.service';

@Controller('info')
export class InfoController {
  constructor(
    private readonly infoService: InfoService,
    private readonly dataSource: DataSource,
  ) {
  }

  @Get()
  async getInfo() {
    return this.dataSource.transaction(manager => {
      return this.infoService.withTransaction(manager, { excluded: [CacheService] }).getInfo();
    });
  }
}
// ./example/src/transfer/info.service.ts

import { Injectable } from '@nestjs/common';
import { TransactionFor } from '../../../lib/with-transaction';
import { CacheService } from './cache.service';
import { ModuleRef } from '@nestjs/core';

@Injectable()
export class InfoService extends TransactionFor<InfoService> {
  constructor(
    private readonly cacheService: CacheService,
    moduleRef: ModuleRef,
  ) {
    super(moduleRef);
  }

  async getInfo() {
    return [];
  }
}

Well, to use transactions with this package you must inject ModuleRef into your provider and extends your provider from the TransactionFor class.

Then in your controller you must create a transaction entity manager and inject this to your providers with .withTransaction(manager) method. This method will recreate you provider and all its dependencies and do what you want.