Reputation: 15
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
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