JSPM

@myfunc/prisma-transactional

0.1.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 2
  • Score
    100M100P100Q39687F
  • License MIT

Package Exports

  • @myfunc/prisma-transactional
  • @myfunc/prisma-transactional/dist/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@myfunc/prisma-transactional) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

Prisma Transactional

Package contains @PrismaTransactional decorator that wraps all prisma queries to a single transaction. In case of overlapping several transactions they will be merged.

Use in production at your own risk. A decorator is being actively used on production environment with no issues, but I strictly recommend to wait for a stable release.

How to setup in NestJS application

Install a package

npm i @myfunc/prisma-transactional

Patch your PrismaClient with patchPrismaTx(client, config)

import { patchPrismaTx } from '@myfunc/prisma-transactional'; // Import
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  constructor() {
    super();
    // Patch and return the substituted version.
    return patchPrismaTx(this, {
      enableLogging: true,
      customLogger: Logger,
    });
  }

  async onModuleInit() {
    await this.$connect();
  }
}

Now you can use PrismaTransactional.

Run example

In Example application described all possible decorator's use cases. For running example app, please add .env file and provide DB connection string.

npm i
npm run dev

How to use the decorator

You can add decorator to any class-method. All queries inside will be wrapped in a single transaction.

On any unhandled error all changed will be rolled back.

BE CAREFUL when using it, all queries inside transaction will be isolated and can lead to deadlock.

Example

  // Now all queries (including nested queries in methods) will be executed in transaction
  @PrismaTransactional() 
  private async addPoints(userId: string, amount: number) {
    const { balance } = await this.getBalance(userId);
    const newBalance = await this.prisma.user.update({
      select: {
        balance: true,
      },
      where: { id: userId },
      data: { balance: roundBalance(balance + amount) },
    });
    return {
      newBalance
    };
  }

To handle success commit you can put the following code anywhere in the code. If there is no transaction, a callback will be executed immediately.

PrismaTransactional.onSuccess(() => {
  this.notifyBalanceUpdated(balance!, args._notificationDelay);
});

Also, you can add many callbacks. All callbacks are stored in a stack under the hood.

You can execute all in transaction with no decorator.

PrismaTransactional.execute(async () => {
  await this.prisma.users.findMany({});
  await this.prisma.users.deleteMany({});
});

or

const result = await PrismaTransactional.execute(async () => {
  const result = await this.prisma.users.findMany({});
  await this.prisma.users.deleteMany({});
  return result;
});

Plans

  • Get rid of hardcoded values and make them configurable. "TRANSACTION_TIMEOUT"
  • Implement ESLint rule for nested prisma queries that might be unintentionally executed in transaction. That means a developer will be aknowledged about possible transaction wrapping and force him to add an eslint-ignore comment.
  • Add tests.
  • Add express.js examples.