You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When a PrismaTestingHelper instance is instantiated using a Prisma Client extension that overrides the $transaction method, methods like $queryRaw fail with a TypeError.
In my case, I'm overriding the $transaction method to implement automatic retries when Postgres serialization errors occur. The issue seems to arise when a non-writable, non-configurable own data property is requested through a PrismaTestingHelper's internal proxyClient. In this case, the proxy fails to return the actual value.
Example
The below TypeScript example reproduces the issue.
import{PrismaTestingHelper}from'@chax-at/transactional-prisma-testing';import{PrismaClient}from'@prisma/client';constprisma=newPrismaClient();// A contrived example in which the `$transaction` method is overriden// Here, the extension simply calls the `$transaction` method of the original client// I do something similar to implement automatic retries on Postgres transaction serialization failuresconstxprisma=prisma.$extends({client: {$transaction(...args: Parameters<typeofprisma.$transaction>){returnprisma.$transaction.apply(prisma,args);},}as{$transaction: typeofprisma.$transaction},});constprismaTestingHelper=newPrismaTestingHelper(xprismaasunknownasPrismaClient,);construnTestTx=async()=>{try{awaitprismaTestingHelper.startNewTransaction();constprismaProxy=prismaTestingHelper.getProxyClient();awaitprismaProxy.$queryRaw`select 1;`;}catch(err){console.error(err);}finally{prismaTestingHelper.rollbackCurrentTransaction();awaitprisma.$disconnect();awaitxprisma.$disconnect();}};runTestTx();
TypeError: 'get' on proxy: property '_extensions' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '#' but got '#')
Analysis
After stepping through the above example with a debugger, here's what I think is going on.
When prismaProxy.$queryRaw is called, the proxy's _extensions property is accessed somewhere in the call stack. Once that happens, the proxy's get handler is called.
// The property does not exist on the transaction client, relay to original client
returnReflect.get(target,prop,receiver);
},
});
Because the _extensions property exists, it gets reflected to prismaTestingHelper.currentPrismaTransactionClient. This is problematic because the _extensions property in the currentPrismaTransactionClient is not the same as the one in the original prismaClient provided in the constructor. I'm not exactly sure why.
Potential Solution
If we explicitly check whether a given property is non-configurable, non-writable on the target of the proxy, we can make sure we always relay the get to the original client. When I try adding the check, tests involving $queryRaw or $executeRaw work without throwing an error. I've searched around and found similar code here.
this.proxyClient=newProxy(prismaClient,{get(target,prop,receiver){// -------------------------------------------------------------------------------// Add the following linesconstdescriptor=Object.getOwnPropertyDescriptor(target,prop);if(descriptor&&!descriptor.configurable&&!descriptor.writable){// This property is not configurable and not writable, relay to original clientreturnReflect.get(target,prop,receiver);}// -------------------------------------------------------------------------------if(prismaTestingHelper.currentPrismaTransactionClient==null){// No transaction active, relay to original clientreturnReflect.get(target,prop,receiver);}if(prop==='$transaction'){returnprismaTestingHelper.transactionProxyFunction.bind(prismaTestingHelper);}if((prismaTestingHelper.currentPrismaTransactionClientasany)[prop]!=null){constret=Reflect.get(prismaTestingHelper.currentPrismaTransactionClient,prop,receiver);// Check whether the return value looks like a prisma delegate (by checking whether it has a findFirst function)if(typeofret==='object'&&'findFirst'inret&&typeofret.findFirst==='function'){returnprismaTestingHelper.getPrismaDelegateProxy(ret);}returnret;}// The property does not exist on the transaction client, relay to original clientreturnReflect.get(target,prop,receiver);},});
Thoughts?
The text was updated successfully, but these errors were encountered:
liu-ronny
changed the title
The $queryRaw method throws a TypeError when Prisma Client extensions are used
The $queryRaw method throws a TypeError when a Prisma Client extension that overrides the $transaction method is used
Nov 2, 2024
Issue
When a
PrismaTestingHelper
instance is instantiated using a Prisma Client extension that overrides the$transaction
method, methods like$queryRaw
fail with aTypeError
.In my case, I'm overriding the
$transaction
method to implement automatic retries when Postgres serialization errors occur. The issue seems to arise when a non-writable, non-configurable own data property is requested through aPrismaTestingHelper
's internalproxyClient
. In this case, the proxy fails to return the actual value.Example
The below TypeScript example reproduces the issue.
Analysis
After stepping through the above example with a debugger, here's what I think is going on.
When
prismaProxy.$queryRaw
is called, the proxy's_extensions
property is accessed somewhere in the call stack. Once that happens, the proxy'sget
handler is called.transactional-prisma-testing/src/prisma.testing.helper.ts
Lines 26 to 49 in d43094a
Because the
_extensions
property exists, it gets reflected toprismaTestingHelper.currentPrismaTransactionClient
. This is problematic because the_extensions
property in thecurrentPrismaTransactionClient
is not the same as the one in the originalprismaClient
provided in the constructor. I'm not exactly sure why.Potential Solution
If we explicitly check whether a given property is non-configurable, non-writable on the target of the proxy, we can make sure we always relay the
get
to the original client. When I try adding the check, tests involving$queryRaw
or$executeRaw
work without throwing an error. I've searched around and found similar code here.Thoughts?
The text was updated successfully, but these errors were encountered: