Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Passing an extended PrismaClient breaks PrismaTestingHelper type #10

Closed
asfaltboy opened this issue Dec 17, 2023 · 7 comments
Closed

Passing an extended PrismaClient breaks PrismaTestingHelper type #10

asfaltboy opened this issue Dec 17, 2023 · 7 comments

Comments

@asfaltboy
Copy link

asfaltboy commented Dec 17, 2023

Using a client with extensions (e.g the prisma-cursorstream extension, but also others) breaks the expected type parameters of PrismaTestingHelper.

Diagnostics:
 Argument of type 'DynamicClientExtensionThis<TypeMap<InternalArgs & { result: {}; model: { $allModels: { cursorStream: () => <T, A extends Args<T, "findMany">, R extends
  Result<T, A, "findMany">[number], C extends ((dataset: R[]) => Promise<...>) | undefined>(this: T, findManyArgs: A, { batchSize, prefill, batchTransformer }?: { ...;...
 ' is not assignable to parameter of type 'PrismaClient<PrismaClientOptions, never, DefaultArgs>'.
  Type 'DynamicClientExtensionThis<TypeMap<InternalArgs & { result: {}; model: { $allModels: { cursorStream: () => <T, A extends Args<T, "findMany">, R extends Result<T,
  A, "findMany">[number], C extends ((dataset: R[]) => Promise<...>) | undefined>(this: T, findManyArgs: A, { batchSize, prefill, batchTransformer }?: { ...;...' is missi
 ng the following properties from type 'PrismaClient<PrismaClientOptions, never, DefaultArgs>': $on, $use [2345]

It seems to always be missing $on, $use properties, likely due to this limitation

To work around this I had to cast it to unknown and then to PrismaClient before passing the extended client to the helper, and when getting it back from the proxy:

import cursorStream from 'prisma-cursorstream';

export let prismaClient;
let prismaTestingHelper: PrismaTestingHelper<PrismaClient> | undefined;

beforeEach(async () => {
  if (prismaTestingHelper == null) {
    const originalPrismaClient = new PrismaClient().$extends(cursorStream);
    prismaTestingHelper = new PrismaTestingHelper(
      originalPrismaClient  as unknown as PrismaClient
    );

    const proxyClient = prismaTestingHelper.getProxyClient() as unknown;
    db = proxyClient as typeof originalPrismaClient;
  }

  await prismaTestingHelper.startNewTransaction();
})

Could we narrow the expected object interface somehow?

@alexjesp
Copy link

Seems related? prisma/prisma#17948

@Valerionn
Copy link
Member

Valerionn commented Dec 19, 2023

I have changed the types a bit and published a new version that you can install using

npm i -D @chax-at/transactional-prisma-testing@1.1.1-broader-types.1

In my simple example this works - however, I personally don't use client extensions, so could you please verify that this solution works for you? Afterwards I can publicly publish version 1.1.1 with this fix.

@asfaltboy
Copy link
Author

Thanks for addressing so quickly! I have tested the above fix version, and types do work correctly now 🎉

That said, I encountered another issue when using the prisma-cursorstream extension in particular, an error appears when I try to use it in a test that is rolled back:

FAIL  src/thing.integration.ts (330 MB heap size)
   thing  should format a JSONL stream

    TypeError [ERR_INVALID_ARG_TYPE]: The "iterable" argument must be an instance of Iterable. Received an instance of Object

      105 |   });
      106 |
    > 107 |   return Readable.from(
          |                   ^
      108 |     db.thing.cursorStream(
      109 |       {
      110 |         where: {

      at getStream (src/thing.ts:107:19)
      at async Object.<anonymous> (src/thing.integration.ts:288:24)

I mention this here, because the same code works fine on its own, or when testing and not using PrismaTestingHelper.

Would you possibly have more time to look more into this? Should I create a separate ticket? Any other info I could provide, I'm thinking it would be nice to add some simple test cases perhaps?

@Valerionn
Copy link
Member

I think this happens because the PrismaTestingHelper wraps the cursorStream call and assumes it returns a Promise like every other Prisma function, but cursorStream doesn't return a Promise.

You can (temporarily) add await to your code above, something like

return Readable.from(
await db.thing.cursorStream( // ...

to confirm my suspicion. If this fixes the problem, then I can add a denylist to the configuration, so that PrismaTestingHelper won't wrap certain calls like this one (since adding await everywhere is obviously not a good fix).

However, it might be possible that prisma-cursorstream always uses the original client (i.e. will always return an empty result since it's not querying inside the transaction). If this is the case, then the problem is more complicated, and I can't guarantee that I find time to look into this (but a simple test case for this would definitely be helpful if it doesn't return anything).

@asfaltboy
Copy link
Author

asfaltboy commented Dec 20, 2023

Hmm, something changed with the await, but I'm not sure what it means:

I'm getting back a Readable instance that looks like this:

    iterable is: Readable {
      _readableState: ReadableState {
        state: 6193,
        highWaterMark: 1,
        buffer: BufferList { head: null, tail: null, length: 0 },
        length: 0,
        pipes: [],
        flowing: null,
        errored: null,
        defaultEncoding: 'utf8',
        awaitDrainWriters: null,
        decoder: null,
        encoding: null,
        [Symbol(kPaused)]: null
      },
      _events: [Object: null prototype] {},
      _eventsCount: 0,
      _maxListeners: undefined,
      _read: [Function (anonymous)],
      _destroy: [Function (anonymous)],
      [Symbol(kCapture)]: false
    }

But trying to access it through iterable.toArray() returns

    TypeError: Cannot read properties of undefined (reading 'findMany')

🤔 sorry I'm not very proficient at Typescript/Javascript so might be missing something obvious?

I'm guessing that by trying to read from the readable, I'm accessing the underlying result of awaiting the cursorStream, which is a call to findMany or something... but why is it undefined?

Update: confirmed that the result of calling await db.thing.cursorStream() in the test, returns undefined

@Valerionn
Copy link
Member

That's probably some weird interaction with the cursorstream package. Unfortunately, I currently don't have the time to debug this in detail (especially with Christmas coming up), and I also can't really give you a time frame when I can get to it.

You could try version 0.6.0 as a workaround because it uses a slightly different architecture - but I fear that this won't work either.

If you can provide me a full repository to test this case (i.e. a Repo that I can clone, run npm install, run Prisma push and then simply run npm test to observe the error), then I can try to look at it again in the debugger to find out if it's something that I can fix.

@Valerionn
Copy link
Member

I've just released v1.2.0 with the broader types which fixes the typing problems at least and generally allows extended PrismaClients.

I've closed the issue for now - but feel free to reopen it or create a new one if the issue is still relevant and you have a full reproduction repo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants