Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic Object Types #61

Open
jpulec opened this issue Jun 3, 2021 · 2 comments
Open

Dynamic Object Types #61

jpulec opened this issue Jun 3, 2021 · 2 comments

Comments

@jpulec
Copy link

jpulec commented Jun 3, 2021

I've looked around and don't think this is possible to do given how fishery is set up, but figured I would mention it.

I'm wondering if it makes sense for fishery to allow dynamic return types, based on passed in arguments, possibly transientParams. One thing that a lot of ORMs support is returning different shapes of objects, based on passed in arguments. For example prisma only returns scalar values from the database by default, but will do database joins and nest objects if given the include parameter.

const user = await prisma.user.findUnique({
   where: {
     id: '1',  
   },
});

user.accountId // Value is set
user.account // Type error and undefined since this was not included

const userWithAccount = await prisma.user.findUnique({
   where: {
     id: '1',  
   },
   include: {
      account: true,
   }
}); 

user.account // No longer a typeerror

Is this something fishery would be interested in trying to support?

@stevehanson
Copy link
Contributor

@jpulec thanks for bringing this up! I was just looking at Prisma's types a few weeks ago and agree that they provide a lot of flexibility. If we could find a way to incorporate something like that into Fishery, I think that would be great. I haven't explored this much yet beyond looking through Prisma's implementation and am not even sure yet what would be feasible, but I welcome any contributions or thoughts in this area!

Prisma's implementation is unique and complicated, as they dynamically create the types for each model and hide them in node_modules. Since the types are generated for each model, they're simpler than they might otherwise be, but they are still too complex for most TS users (including myself) to be able to easily understand (see below).

I see this feature being complex to implement, so I'm also open to other approaches for Fishery that are simpler but still generally achieve the same goal. Eg. multiple factories that are related to each other but with different return types (eg. userFactory and userWithPostsFactory).

I've included my notes from my recent Prisma research below. Thanks again for bringing this up!

Prisma research

// returns type "User & { posts: Post[] }"
const users = await prisma.user.findMany({ include: { posts: true } });

They achieve all this by generating custom types per DB model when you update the ORM schema file and placing the types in node_modules/.prisma/index.d.ts.

Here’s the typing for user.findMany:

findMany<T extends UserFindManyArgs>(
  args?: SelectSubset<T, UserFindManyArgs>
): CheckSelect<T, PrismaPromise<Array<User>>, PrismaPromise<Array<UserGetPayload<T>>>>

UserGetPayload is where the magic of returning the correct type based on the include or select args takes place:

  export type UserGetPayload<
    S extends boolean | null | undefined | UserArgs,
    U = keyof S
      > = S extends true
        ? User
    : S extends undefined
    ? never
    : S extends UserArgs | UserFindManyArgs
    ?'include' extends U
    ? User  & {
    [P in TrueKeys<S['include']>]: 
          P extends 'posts'
        ? Array < PostGetPayload<S['include'][P]>>  : never
  } 
    : 'select' extends U
    ? {
    [P in TrueKeys<S['select']>]: P extends keyof User ?User [P]
  : 
          P extends 'posts'
        ? Array < PostGetPayload<S['select'][P]>>  : never
  } 
    : User
  : User

@stevehanson
Copy link
Contributor

I've started brainstorming a refactoring of this library that I think will provide flexibility to accomplish most of what you are proposing here. See #71 (comment).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants