I’m developing a server with prisma and mongodb along with some generators for interfaces and zod schemas. I want to create 5 base functions for create, findMany, findUnique, update and delete that will work for all models based on parameters. I’ve tried many different ways but I always get a This expression is not callable.
error. Another thing I’d like is to be able to not have both the generic and normal parameter for the model name (I tried removing the generic but then I lost the type safety on the createArgs).
Here’s my code for the create function I have now (other functions are similar):
import { Prisma, PrismaClient } from "@prisma/client";
import { ZodError } from "zod";
import { prismaClient } from "..";
type PrismaModelName = Uncapitalize<Prisma.ModelName>;
type CreateArgs<T extends PrismaModelName> = T extends keyof PrismaClient
? Parameters<PrismaClient[T]["create"]>[0]
: never;
function getDelegate(model: PrismaModelName) {
return prismaClient[model];
}
async function createDocument<Model, ModelName extends PrismaModelName>(
model: ModelName,
createArgs: CreateArgs<ModelName>,
createArgsSchema: Zod.Schema
): Promise<Model | { error: any }> {
try {
createArgsSchema.parse(createArgs);
const delegate = getDelegate(model);
const newDocument = await delegate.create(createArgs);
return newDocument;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
if (error.code === "P2002") {
return { error: "Document already exists." };
}
} else if (error instanceof ZodError) {
return { error: error.flatten() };
}
return { error };
}
}
Here’s the full error:
This expression is not callable.
Each member of the union type '(<T extends TeacherCreateArgs>(args: SelectSubset<T, TeacherCreateArgs<DefaultArgs>>) => Prisma__TeacherClient<GetFindResult<$TeacherPayload<DefaultArgs>, T, {}>, never, DefaultArgs>) | ... 8 more ... | (<T extends RoomCreateArgs>(args: SelectSubset<...>) => Prisma__RoomClient<...>)' has signatures, but none of those signatures are compatible with each other.ts(2349)
I can cast the delegate as any in the create function but that could be problematic for type safety though it does resolve all errors. The code works when I cast it as any. This article seemed relevant: #172270
Here’s my schema.prisma
that can be used to replicate this:
generator client {
provider = "prisma-client-js"
}
generator zod {
provider = "zod-prisma-types"
}
generator typescriptInterfaces {
provider = "prisma-generator-typescript-interfaces"
output = "../types/interfaces.ts"
}
datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
}
enum LessonWeek {
ODD
EVEN
BOTH
}
enum Day {
MONDAY
TUESDAY
WEDNESDAY
THURSDAY
FRIDAY
SATURDAY
SUNDAY
}
model Teacher {
id String @id @default(auto()) @map("_id") @db.ObjectId
// ? should the name be unique?
name String /// @zod.string.min(1, { message: "Името трябва да сърдържа поне 1 символ" })
schoolId String @db.ObjectId
school School @relation(fields: [schoolId], references: [id])
lessons Lesson[] @relation("TeacherLessons")
subjectIds String[] @db.ObjectId
subjects Subject[] @relation(fields: [subjectIds], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([schoolId])
}
model Student {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String /// @zod.string.min(1, { message: "Името трябва да сърдържа поне 1 символ" })
classId String @db.ObjectId
class SchoolClass @relation(fields: [classId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([classId])
}
model Subject {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String /// @zod.string.min(1, { message: "Името трябва да сърдържа поне 1 символ" })
schoolId String @db.ObjectId
school School @relation(fields: [schoolId], references: [id])
lessons Lesson[] @relation("SubjectLessons")
teacherIds String[] @db.ObjectId
teachers Teacher[] @relation(fields: [teacherIds], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([schoolId])
}
model Lesson {
id String @id @default(auto()) @map("_id") @db.ObjectId
lessonNumber Int /// @zod.number.gte(0, { message: "Номерът на часа не може да бъде отрицателно число" })
startTime DateTime
endTime DateTime
lessonWeeks LessonWeek @default(BOTH)
roomId String @db.ObjectId
room Room @relation(fields: [roomId], references: [id])
teacherId String @db.ObjectId
teacher Teacher @relation(fields: [teacherId], references: [id], name: "TeacherLessons")
subjectId String @db.ObjectId
subject Subject @relation(fields: [subjectId], references: [id], name: "SubjectLessons")
scheduleId String @db.ObjectId
schedule Schedule @relation(fields: [scheduleId], references: [id], name: "ScheduleLessons")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// The lesson number should be unique within a schedule and week type
@@unique([lessonNumber, lessonWeeks, scheduleId])
// ? maybe add these indexes?
// @@index([lessonNumber])
// @@index([lessonWeeks])
// @@index([roomId])
@@index([teacherId])
@@index([subjectId])
@@index([scheduleId])
}
// TODO: rename to DailySchedule
model Schedule {
id String @id @default(auto()) @map("_id") @db.ObjectId
day Day
classId String @db.ObjectId
class SchoolClass @relation(fields: [classId], references: [id])
lessons Lesson[] @relation("ScheduleLessons")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// The day should be unique within a class
@@unique([day, classId])
@@index([day])
@@index([classId])
}
model SchoolClass {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String /// @zod.string.min(1, { message: "Името трябва да сърдържа поне 1 символ" })
schoolId String @db.ObjectId
school School @relation(fields: [schoolId], references: [id])
students Student[]
weeklySchedule Schedule[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// The class name should be unique within a school
@@unique([schoolId, name])
@@index([schoolId])
}
model School {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String @unique /// @zod.string.min(1, { message: "Името трябва да сърдържа поне 1 символ" })
classes SchoolClass[]
subjects Subject[]
teachers Teacher[]
buildings Building[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Building {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String /// @zod.string.min(1, { message: "Името трябва да сърдържа поне 1 символ" })
schoolId String @db.ObjectId
school School @relation(fields: [schoolId], references: [id])
floors Floor[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// The building name should be unique within a school
@@unique([schoolId, name])
@@index([schoolId])
}
model Floor {
id String @id @default(auto()) @map("_id") @db.ObjectId
number Int
description String?
planFilename String?
maskFilename String?
buildingId String @db.ObjectId
building Building @relation(fields: [buildingId], references: [id])
rooms Room[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// The floor number should be unique within a building
@@unique([buildingId, number])
@@index([buildingId])
}
model Room {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String /// @zod.string.min(1, { message: "Името трябва да сърдържа поне 1 символ" })
floorId String @db.ObjectId
floor Floor @relation(fields: [floorId], references: [id])
lessons Lesson[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// The room name should be unique within a floor
@@unique([floorId, name])
@@index([floorId])
}
Here’s the example query I used to test with the any cast:
const newSchool = await createDocument<School, "school">(
"school",
{
data: {
name: "School 2",
},
},
SchoolCreateArgsSchema
);
If you don’t want to deal with the zod schemas and other stuff you can remove the generators in the schema.prisma
and the parameter in the code for the zod schema.