DEV Community

Harry Horton
Harry Horton

Posted on

How to wrap a Prisma method and reuse types

Prisma is an easy to use ORM with really useful Typescript types that change the result type of a query based on the options you pass in.

While building out an application, you'll undoubtedly run into a situation where you want to wrap an existing Prisma method with custom logic. You may decide to pass-through the types so that the caller can decide if they want to extend query.

If you're using Prisma's query arguments, you'll also expect to get Prisma's return types.

This is not as easy as it sounds, as simply passing the data (or even passing a generic backed value) does not work as intended.

You may start by trying something like this. Wrapping the todo findMany call with a function that accepts Prisma query args, but modifies the query to do something specific.

// DOES NOT WORK
  specialFindMany<T extends Prisma.TodoFindManyArgs>(args?:T){
    return prisma.todo.findMany({
      ...args,
      where:{
        ...args?.where
        isSpecial: true
      }
    })
  }
Enter fullscreen mode Exit fullscreen mode

This will produce type errors.

Discovery

As with anything in Typescript, if you dig into the types, you can get at what a function actually expects.

Looking at the types for todo.findMany() as of Prisma 2.24.0 you'll find something that looks like this:

findMany<T extends TodoFindManyArgs>(
      args?: SelectSubset<T, TodoFindManyArgs>
    ): CheckSelect<T, PrismaPromise<Array<Todo>>, PrismaPromise<Array<TodoGetPayload<T>>>>
Enter fullscreen mode Exit fullscreen mode

You can see that they're pulling the type from SelectSubsets generics.

Solution

If you copy this type into both your method's declaration as well as the prisma.finyMany generic, you'll get a working typed passthrough.

   async specialFindMany<T extends Prisma.TodoFindManyArgs>(
    args?: Prisma.SelectSubset<T, Prisma.TodoFindManyArgs>,
  ) ){
    // Other custom logic

    const result = await prisma.todo.findMany<
    Prisma.SelectSubset<T, Prisma.SearchFindManyArgs>>(
      {
      ...args!,
      where:{
        ...args?.where
        isSpecial: true
      }
    })

    // Other custom logic

    return result
  }
Enter fullscreen mode Exit fullscreen mode

Without passing in the generic, the types will still fail to pass through (at least in my testing).

Since args is optional, the types will complain about forcing an object through. There may be a better solution to this, but I simply ! forced the args type to exist.

Conclusion

This feels pretty cumbersome, and I'd love to see some more flexible typing in Prisma, but it gets the job done for wrapping database calls.

This solution unfortunately doesn't cover extending the types that go into the Prisma call, so including include or select will not result in proper output types.

If you have a better solution, please let me know in the comments!!!

Top comments (3)

Collapse
 
faigbagirov profile image
Faig Bagirov

Hi, there's some typo I guess

where:{
...args?.where //here, it says: " ',' expected "
isSpecial: true

Anyway thank you for your article, I have similar problem, and now I know where do dig further.

Collapse
 
faigbagirov profile image
Faig Bagirov • Edited

Update:
I have figured out how to write a wrapper for my needs.
Mayme I deed something very stupid but it works the way I need. It checks the type for my wrapper and this type is being updated when I update the schema (you can see it on the picture here dev-to-uploads.s3.amazonaws.com/up...).

There's more, I went further and hid whole prisma data structure as well to make my life easier If I decide to change the ORM:

//Here's just an id and optional object for columns you want to be fetched
getOne(1, {cart_items: true})

function getOne(theId: number, columns?: Prisma.cartsSelect){
const cartId = {id: theId}
const allCarts = await prisma.carts.findUnique({
where: cartId,
select: columns
})

Collapse
 
samyarkd profile image
samyar

great 👌