Prisma adapter
Installation
- npm
- yarn
- pnpm
npm install @nestjs-cls/transactional-adapter-prisma
yarn add @nestjs-cls/transactional-adapter-prisma
pnpm add @nestjs-cls/transactional-adapter-prisma
Registration
ClsModule.forRoot({
plugins: [
new ClsPluginTransactional({
imports: [
// module in which the PrismaClient is provided
PrismaModule
],
adapter: new TransactionalAdapterPrisma({
// the injection token of the PrismaClient
prismaInjectionToken: PrismaService,
// specify the SQL flavor (if using SQL, see below)
sqlFlavor: 'postgresql'
}),
}),
],
}),
The prismaInjectionToken
is the token under which an instance of PrismaClient
provided. Usually, in Nest, this the custom PrismaService
class which extends PrismaClient
and is exported from a custom module.
The sqlFlavor
option is needed to enable nested transaction support via Propagation.Nested
.
Since Prisma does not yet support nested transactions natively, the adapter implements a custom solution via raw queries and SQL SAVEPOINTS. But because different SQL flavors implement the syntax differently, and because Prisma also does not provide a way to introspect the datasource provider name at runtime, we need to specify it manually.
Please note that if the datasource in your Prisma schema and the SQL flavor do not match, syntax errors might be thrown when attempting to use nested transactions.
Typing & usage
The tx
property on the TransactionHost<TransactionalAdapterPrisma>
refers to the transactional PrismaClient
instance when used in a transactional context. It is the instance that is passed to the prisma.$transaction(( tx ) => { ... })
callback.
Outside of a transactional context, it refers to the regular PrismaClient
instance (but is typed as the transactional one).
Example
@Injectable()
class UserService {
constructor(private readonly userRepository: UserRepository) {}
@Transactional()
async runTransaction() {
// both methods are executed in the same transaction
const user = await this.userRepository.createUser('John');
const foundUser = await this.userRepository.getUserById(user.id);
assert(foundUser.id === user.id);
}
}
@Injectable()
class UserRepository {
constructor(
private readonly txHost: TransactionHost<TransactionalAdapterPrisma>,
) {}
async getUserById(id: number) {
// txHost.tx is typed as the transactional PrismaClient
return this.txHost.tx.user.findUnique({ where: { id } });
}
async createUser(name: string) {
return this.txHost.tx.user.create({
data: { name: name, email: `${name}@email.com` },
});
}
}
Custom client type
Since1.1.0
By default, the adapter assumes that the Prisma client is available as @prisma/client
. If you have a different setup, or you use some Prisma client extensions, you can provide a custom type for the client as a generic parameter of the adapter.
TransactionalAdapterPrisma<CustomPrismaClient>;
This type will need to be used whenever you inject the TransactionHost
or Transaction
private readonly txHost: TransactionHost<TransactionalAdapterPrisma<CustomPrismaClient>>
Which becomes pretty verbose, so it's recommended to create a custom type alias for the adapter.
Please make sure you set up the module with the custom prisma client and not the default one, otherwise you would get a runtime error.
new ClsPluginTransactional({
imports: [
// module in which the PrismaClient is provided
PrismaModule
],
adapter: new TransactionalAdapterPrisma({
// the injection token of the PrismaClient
prismaInjectionToken: CUSTOM_PRISMA_CLIENT_TOKEN,
}),
}),