question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Specifying the "select" param in a query using a variable with a defined type breaks types

See original GitHub issue

Bug description

After upgrading Prisma from 2.2 to 2.5, I’m now running into an issue where, when I try to provide the select parameter using a typed variable rather than inline code in a query, the result of the query no longer has any type information associated with it.

For example, I have a User type that has a number of properties, like id, createdAt, etc.

If I attempt to do a query with the select being defined inline, types are present on the result as expected:

image

If I now define my selection property using a typed variable, however (with the type corresponding to to the type expected by the Prisma for the query), and the pass that in instead, the result no longer has any properties, being typed as an empty object instead:

image

Oddly, if I remove the typing on the select variable, type information returns to the result (but I no longer have type suggestions when composing the select variable)

image

How to reproduce

Take any query, extract the select property into a typed variable.

Expected behavior

I expect the result of the query to have type information that corresponds to what the select variable is set to.

Prisma information

This appears to apply to any and all schemas, types, and queries.

Environment & setup

  • OS: Windows
  • Database: PostgreSQL
  • Node.js version: 10.16
  • Prisma version: 2.5.1

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:4
  • Comments:26 (14 by maintainers)

github_iconTop GitHub Comments

16reactions
timsuchanekcommented, Jan 14, 2021

Thanks for all the comments. We are talking about two distinct problems here.

1. Problem: How do I separately construct the select or include argument and not just inline?

2. Problem: How do I create a wrapper function around any Prisma query, like findMany that is typesafe?

These problems require separate solutions. In the following, I’ll show how they’re solvable today and also mention a potential utility type that we might want to generate in the client to save you some typing.

1. Problem: Dynamically construct select or include

This is, what @artemzakharov and @vh13294 requested. This can be achieved with a little helper function, as Harshit already noted. Here called makeUserSelect:

import { PrismaClient, Prisma } from './@prisma/client'

function makeUserSelect<T extends Prisma.UserSelect>(
  select: Prisma.Subset<T, Prisma.UserSelect>,
): T {
  return select
}

const select = makeUserSelect({ id: true })

const user = await prisma.user.findFirst({
  where: {
    id: 'id',
  },
  select,
})

const id = user?.id

The same for include:

function makeUserInclude<T extends Prisma.UserInclude>(
  include: Prisma.Subset<T, Prisma.UserInclude>,
): T {
  return include
}

const include = makeUserInclude({
  likes: true
})

const user = await prisma.user.findFirst({
  where: {
    id: 'id',
  },
  include,
})

const likes = user?.likes

We can even do this for the whole args, here an example for findMany:

function makeUserFindManyArgs<T extends Prisma.UserFindManyArgs>(
  include: Prisma.Subset<T, Prisma.UserFindManyArgs>,
): T {
  return include
}

const args = makeUserFindManyArgs({
  include: {
    likes: true
  }
})

const users = await prisma.user.findMany(args)

const likes = users[0].likes

With our current knowledge, we need to create one function per type we want to “make”. We will later investigate if we could simplify that.

2. Problem: How do I create a wrapper function around any Prisma query, like findMany that is typesafe?

This is, what @MichalLytek and @Sytten talk about. This can be done without any helper type. We can make this happen both for select and include, but not the whole args:

Just passing in select, as requested by @MichalLytek

function findNewPrismaUsersInNearbySelect<S extends Prisma.UserSelect>(
  select: Prisma.Subset<S, Prisma.UserSelect>,
) {
  return prisma.user.findMany<{ select: S }>({ select, where: {} })
}

const users = await findNewPrismaUsersInNerbySelect({ id: true })
users[0].id

The same for include:

function findNewPrismaUsersInNearbyInclude<S extends Prisma.UserInclude>(
  include: Prisma.Subset<S, Prisma.UserInclude>,
) {
  return prisma.user.findMany<{ include: S }>({ include })
}
const users = await findNewPrismaUsersInNearbyInclude({ posts: true })
users[0].posts

We can’t do this for the whole args here:

function findNewPrismaUsersInNearby<T extends Prisma.UserFindManyArgs>(
  args: Prisma.Subset<T, Prisma.UserFindManyArgs>,
) {
  return prisma.user.findMany({...args, where: {}})
}

const users = await findNewPrismaUsersInNearby({ select: { id: true } })
// types will be incorrect

But that also hasn’t been asked 🙂

Does this solve your problem? Please let us know 🙏

Next steps for us at Prisma:

  • Document this
  • Find out, which make... types we want to expose
9reactions
millspcommented, Jan 18, 2021

Definitely, that’s the workaround the validator uses:

declare function validator<V>(): <S>(select: Exact<Narrow<S, V>, V>) => S;

I’m working on a codebase that relies (too) much on this - it’s heavy for my cognitive load. But there is a potential PR candidate that should solve this problem in the near (?) future: https://github.com/microsoft/TypeScript/pull/26349

Though it’s not certain that this will solve our use case here. It will depend on whether we will be allowed to use the _ as a default parameter or not. A few people already have asked for this. In a perfect world, we would be able to do:

declare function validator<V, S = _>(select: Exact<Narrow<S, V>, V>) => S;

Time will tell, if this is not supported, then we will fallback to this issue https://github.com/microsoft/TypeScript/issues/10571

The TypeScript team has added this on their roadmap, but no date is yet set. This is one of the most exciting (missing) features for TypeScript today, IMO.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Use parameters in queries, forms, and reports
Specify parameter data types · With the query open in Design view, on the Design tab, in the Show/Hide group, click Parameters. ·...
Read more >
Using Parameters in Queries - Jaspersoft Community
Parameters can be used in SQL queries to filter records in a where condition or to add/replace pieces of raw SQL or even...
Read more >
SQL*Plus Substitution Variables - DEFINE ... - Oracle Blogs
This post shows how substitution variables can replace hard-coded text in Oracle SQL and SQL*Plus statements.
Read more >
Running parameterized queries | BigQuery - Google Cloud
To use a struct in a query parameter set the type to STRUCT<T> where T defines the fields and types within the struct....
Read more >
mysqli_stmt::bind_param - Manual - PHP
Bind variables for the parameter markers in the SQL statement prepared by ... more characters which specify the types for the corresponding bind...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found