Adrian Abadín
Adrian Abadín

Reputation: 15

Generic DAO Service using prisma js

Hi Im tryng to build a DAO class using prisma aplyng SOLID principles. The idea is to build generic functions and an abstract class so i can aply them on the diferen modules by dependence injection. So for example when i need to build my Users.Service.ts Ill extend GenericClass and inyect the funcions i need for thar module create, update and so on. But im having probems with typescript. At the botom is the entire code for the Generic service. The error I get from typescript is :

This expression is not callable. Each member of the union type '((args: SelectSubset<T, UsersCreateArgs>) => Prisma__UsersClient<UsersGetPayload, never>) | ((args: SelectSubset<...>) => Prisma__ProductsClient<...>) | ((args: SelectSubset<...>) => Prisma__CartsClient<...>)' has signatures, but none of those signatures are compatible with each other

What i understund of it is that typescript can be certain about PrismaClient[T] being of the same kind that dto:GenericDto<T>

I ve builded some validations for it but, doesnt seem to work. Probably there are a simpler solution for it, but i dont see it . The abstract class is not yet implemented because im having problems with the firs function. and the function is outside the class so i can customize my classes depending on the need.

This is the only implementation that works, but it sucks on scalability Does someone knows how to do this the right way? Thanks so much

import { Prisma, PrismaClient } from '@prisma/client'
export interface IParam<T extends Lowercase<Prisma.ModelName>> {
  client: PrismaClient[T]
  data: Dto<T>
  model: T
}

type Dto<T extends Lowercase<Prisma.ModelName>> = PrismaClient[T] extends { create: (args: { data: infer U }) => Promise<unknown> } ? U : never
type Models<T extends keyof PrismaClient> =
  PrismaClient[T] extends { create: infer U }
    ? { create: U }
    : never

type createMethodType<T extends Lowercase<Prisma.ModelName>> = (dto: Dto<T>, client: Models<T>) => Promise<unknown>
class CreatorObject {
  constructor (
    public carts = async (dto: Dto<'carts'>, client: Models<'carts'>): Promise<any> => {
      return await client.create({ data: dto })
    },
    public users = async (dto: Dto<'users'>, client: Models<'users'>): Promise<any> => {
      return await client.create({ data: dto })
    },
    public products = async (dto: Dto<'products'>, client: Models<'products'>): Promise<any> => {
      return await client.create({ data: dto })
    }
  ) {}
}
export const clousure = (model: keyof CreatorObject): any => {
  const creatorObject = new CreatorObject()

  const create: createMethodType<typeof model> = async (dto, client) => {
    const response = creatorObject[model]
    return response
  }
  return { create }
}

Upvotes: 1

Views: 1173

Answers (1)

Adrian Abad&#237;n
Adrian Abad&#237;n

Reputation: 15

Ok, this is the best I could Comme up with. Ive implemented a model extension for all models with the queries i needer bassed upn this discussion. Given name of a model, get its fields The only residual problem im seemed to have is that when i try to define the exact function params on the create, update and the rest of methods used I still ve the same problem as before. But by using the Function type i was able to fullfill my needs but at expense of loosing some type safety. I leave the code implemented here.

import { PrismaClient, Prisma } from '@prisma/client'
import { logger } from '../logger/logger.service'
import { ResponseObject } from '../entities'
import { type IResponse } from '../index'
export class PrismaSingleton {
  static Instance: any
  constructor (
    public prisma = new PrismaClient().$extends({
      model: {
        $allModels: {
          async createGeneric<T>(
            this: T & { create: Function },
            x: Prisma.Args<T, 'create' >['data']
          ): Promise<IResponse> {
            const modelName = Prisma.getExtensionContext(this).name
            try {
              const response = await this.create({ data: x })
              return new ResponseObject(null, true, response)
            } catch (error) {
              logger.error({
                function: `${modelName as string}CreateGeneric`,
                error
              })
              return new ResponseObject(error, false, null)
            }
          },
          async updateGeneric<T>(
            this: T & { update: Function },
            data: Prisma.Args<T, 'update'>['data'],
            id: string
          ): Promise<IResponse> {
            const model = Prisma.getExtensionContext(this).name
            try {
              const response = await this.update({ where: { id }, data })
              return new ResponseObject(null, true, response)
            } catch (error) {
              logger.error({
                function: `${model as string}updateGeneric`,
                error
              })
              return new ResponseObject(error, false, null)
            }
          },
          async deleteGeneric<T>(
            this: T & { delete: Function },
            id: string
          ): Promise<IResponse> {
            const model = Prisma.getExtensionContext(this).name
            try {
              const response = await this.delete({ where: { id } })
              return new ResponseObject(null, true, response)
            } catch (error) {
              logger.error({
                function: `${model as string}DeleteGeneric`,
                error
              })
              return new ResponseObject(error, false, null)
            }
          },
          async listGeneric <T>(
            this: T & { findMany: Function }
          ): Promise<IResponse> {
            const model = Prisma.getExtensionContext(this).name
            try {
              const response = await this.findMany()
              return new ResponseObject(null, true, response)
            } catch (error) {
              logger.error({
                function: `${model as string}ListGeneric`, error
              })
              return new ResponseObject(error, false, null)
            }
          },
          async getByIdGeneric <T>(
            this: T & { findUnique: Function },
            id: string
          ): Promise<IResponse> {
            const model = Prisma.getExtensionContext(this).name
            try {
              const response = await this.findUnique({ where: { id } })
              return new ResponseObject(null, true, response)
            } catch (error) {
              logger.error({
                function: `${model as string}GetByIdGeneric`, error
              })
              return new ResponseObject(error, false, null)
            }
          }
        }
      }
    })

  ) {
    if (PrismaSingleton.Instance !== undefined) return PrismaSingleton.Instance
    else PrismaSingleton.Instance = this
    return this
  }
}

I hope this serves the comunity and if anyone finds how to get rid on the Function type ill apretiate it Ive tryed a generic like

type GetParams<T>=T extends {create:infer U} ? U : never
// OR even
type GetParams<T>=T extends {create:(args:infer U}:any=> ? U : never

Upvotes: 0

Related Questions