Creating dynamic functions for CRUD operations for all models with type safety

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.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật