prisma / prisma

Next-generation ORM for Node.js & TypeScript | PostgreSQL, MySQL, MariaDB, SQL Server, SQLite, MongoDB and CockroachDB
https://www.prisma.io
Apache License 2.0
39.15k stars 1.53k forks source link

Support extended Prisma Clients on `TransactionClient` type #20738

Open tsa96 opened 1 year ago

tsa96 commented 1 year ago

Problem

I want to be able to be able to execute parts of an interactive transaction within separate functions, so I pass the transaction version of the PrismaClient to a function like so:

const result = await prisma.$transaction(tx => foo(tx))

Without Client Extensions, the TransactionClient works great. I can give the type of tx on foo just with

async function foo(tx: Prisma.TransactionClient) { ... }

With an extended Prisma client however, whilst tx should be an extended instance (https://github.com/prisma/prisma/pull/19565), I don't have an immediate way to get the type of it, so the type of tx on foo in the above example won't include my extensions.

Suggested solution

TransactionClient is defined as

export type TransactionClient = Omit<Prisma.DefaultPrismaClient, runtime.ITXClientDenyList>

so I can get the type currently using

async function foo(tx: Omit<ExtendedPrismaService, ITXClientDenyList>) { ... }

but I'd rather not do this all over the place, plus importing ITXClientDenyList from @prisma/client/runtime is quite ugly.

It'd be nice to have a type in the generated client types such as

export type ExtendedTransactionClient<T> = Omit<T, runtime.ITXClientDenyList>

or even just change the current TransactionClient (this isn't a breaking change, right...?)

export type TransactionClient<T = Prisma.DefaultPrismaClient> = Omit<T, runtime.ITXClientDenyList>

Alternatives

Importing ITXClientDenyList does work it's just ugly, and hopefully relatively simple to support a generic in TransactionClient as above. There may well have be an easier way of doing this with the current types that I've missed!

Edit: Just realised I made this when still on Prisma 4 (5 was already out!). Like @basememara says, Prisma 5 actually worsens the issue by no longer exporting ITXClientDenyList. For now I've resolved to using the very ugly

export type ExtendedPrismaServiceTransaction = Omit<
  ExtendedPrismaService,
  '$extends' | '$transaction' | '$disconnect' | '$connect' | '$on' | '$use'
>;
basememara commented 1 year ago

Importing ITXClientDenyList doesn't to work with Prisma v5. How can this be achieved now?

Just some more context... I'm trying to pass a transaction into another method or fallback to the default client:

async doSomething(transaction: Omit<PrismaClient, ITXClientDenyList> | null = null) {
    return await (transaction || this.prisma).myTable.upsert(...);
}

async somewhereElse() {
    return await this.prisma.$transaction(async (transaction) => {
      const test = await this. doSomething(transaction);
      await transaction.otherTable.delete({ where: { id } });
      return test;
    });
}

async anotherPlace() {
    return await this. doSomething();
}

I don't want to have to manually define the list:

type MyITXClientDenyList = '$connect' | '$disconnect' | '$on' | '$transaction' | '$use' | '$extends';
MarcusCemes commented 10 months ago

It's possible to extract/infer the type from the generated PrismaClient type, which you can re-export and use around your codebase. It's relatively simple and works just fine, and the implementation of the type stays internal to Prisma.

// src/db.ts
export const db = createClient();

export type DbClient = ReturnType<typeof createClient>;
export type DbTransactionClient = Parameters<Parameters<DbClient["$transaction"]>[0]>[0];

function createClient() {
    return new PrismaClient().$extends({ ... });
}
hiramhuang commented 1 month ago

The Prisma.TransactionClient type is now accessible (I'm on v5.15.0). Should this issue be considered closed?

tsa96 commented 1 month ago

The Prisma.TransactionClient type is now accessible (I'm on v5.15.0). Should this issue be considered closed?

Prisma.TransactionClient has always been accessible, it's defined as

export type TransactionClient = Omit<Prisma.DefaultPrismaClient, runtime.ITXClientDenyList>

The issue that it cannot be used with client extensions, since the prisma client won't be assignable to Prisma.DefaultPrismaClient.

FWIW this is a very minor issue; MarcusCemes's Parameters<Parameters<DbClient["$transaction"]>[0]>[0]; type is a reasonable workaround. (edit: @jpenna makes to a fair point, but let's not bog this thread down)

jpenna commented 2 weeks ago

FWIW this is a very minor issue; MarcusCemes's Parameters<Parameters<DbClient["$transaction"]>[0]>[0]; type is a reasonable workaround.

It is not a reasonable workaround. It needs a lot of time looking for a solution to this problem and it is only "documented" in this issue. Besides, although it is one line, it is a very convoluted type.