Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
December 20, 2022 11:40 am GMT

Dynamic return type based on input parameter in TypeScript like Prisma

Prisma does a good job of type safety

After using TypeORM for many years, I have switched to Prisma recently, as mentioned in one of my posts:

The main reason is that Prisma does a good job of type safety. I will show you the most impressive one for me with classical Post & User schema as below:

model User {  id        Int      @id @default(autoincrement())  createdAt DateTime @default(now())  email     String   @unique  name      String?  role      Role     @default(USER)  posts     Post[]}model Post {  id        Int      @id @default(autoincrement())  createdAt DateTime @default(now())  updatedAt DateTime @updatedAt  published Boolean  @default(false)  title     String   @db.VarChar(255)  author    User?    @relation(fields: [authorId], references: [id])  authorId  Int?}

Lets say you want to find all the published posts, you can easily do that using the below code:

const publishedPosts = await prisma.post.findMany({    where: { published: true }  })

The publishedPosts would be typed as below:

const publishedPosts: Post[]

If you also want Prisma eagerly load the relation of the author field, you can use include to do that like below:

const publishedPosts = await prisma.post.findMany({  where: { published: true },  include: { author: true },})

This time publishedPosts would have the type as below:

const publishedPosts: (Post & {  author: User})[]

In short words, the same function would have different result types according to the input data.

The benefit is that if the query wont include author but you try to access it, you will get the type-checking error immediately in IDE as below instead of getting the runtime undefined error when launching it:

type-check-fail

Then you can fix it right away instead of probably investigating and fixing the bug reported.

type-check-success

This was the first time I was aware of this ability of Typescript. If it is the same for you, keep reading. I will show you how to do that in a simple way.

How to achieve that

In a nutshell, it is done by the powerful type system of Typescript. Prisma really takes full advantage of it to achieve a good type safety as you can see. I think you can even test your understanding level of Typescripts type system by looking at the source code Prisma. So to say, its not that easy. Therefore, I will use a much simple self-contained version tweaked from Prismas source code to illustrate it.

If you want to test the code yourself, you should prepare one project with the aforementioned Post&User schema. You can get that by running the command below:

npx try-prisma --template typescript/rest-nextjs-api-routes
  1. Since the result depends on the true property of include, so we need to have a way to find the truthy key for an object type which could be done below:

    export type TruthyKeys<T> = keyof {  [K in keyof T as T[K] extends false | undefined | null ? never : K]: K;};

    You can simply test it using the below case:

    type TestType = TruthyKeys<{ a: true; b: false; c:null; d:"d" }>;

    the TestType would have type as below:

    type TestType: "a" | "d"
  2. Lets create PostGetPayLoad type to infer the result type from the input:

    import { Post, Prisma, User } from "@prisma/client";type PostGetPayload<S extends Prisma.PostFindManyArgs> = S extends {  include: any;}  ? Post & {      [P in TruthyKeys<S["include"]>]: P extends "author" ? User : never;    }  : Post;

    S is the input parameter passed to the findMany function. Prisma has the generated type Prisma.PostFindManyArgs, which you can extend. With the help of the above-defined TruthyKeys type, we can get our mission done by checking whether the include property contains a truthy author inner property. If so, we will return Post &{ author:User} , Otherwise simply return Post

  3. Finally, you can declare the below dummy function to test the result. It will get you the same type of result as Prismas version.

    declare function findMany<T extends Prisma.PostFindManyArgs>(args: T): Array<PostGetPayload<T>>;

Easter Eggs

Lets say we use include but set the false value to author like below:

const publishedPosts = await prisma.post.findMany({  where: { published: true },  include: { author: false },})

What do you think the result type should be? If you still remember the test case of TruthyKeys, You can be sure that it is the same without the no-including case, which will be Post[].

What do you think about the below one?

const parameter = {    where: { published: true },    include: { author: false },  };const publishedPosts = await prisma.post.findMany(parameter);

Actually, it just extracts the parameter to a variable. Since they are semantically equivalent, they should have the exact same result, right? However, this time the result is different:

const publishedPosts: (Post & {    author: User;})[]

Why? With the help of IDE intelligence, it wont cause you too much to find the clue.

How to fix it to return the right type

If you managed to fix it still using the variable way, then congratulations that you get more understanding of the type system of Typescript!

P.S.ZenStack is built above Prisma - a toolkit for building secure CRUD apps with Next.js + Typescript. Our goal is to let you save time writing boilerplate code and focus on building what matters the user experience.


Original Link: https://dev.to/zenstack/dynamic-return-type-based-on-input-parameter-in-typescript-like-prisma-1292

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To