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

Prisma.validator: with clientExtensions enabled, Typescript can no longer compute types #17767

Closed
jove4015 opened this issue Feb 6, 2023 · 16 comments · Fixed by #19587
Closed
Assignees
Labels
bug/2-confirmed Bug has been reproduced and confirmed. kind/bug A reported bug. team/client Issue for team Client. tech/typescript Issue for tech TypeScript. topic: client types Types in Prisma Client topic: clientExtensions
Milestone

Comments

@jove4015
Copy link

jove4015 commented Feb 6, 2023

Bug description

On the most basic schema, when Client Extensions are enabled, Typescript is no longer able to properly generate types for query results. One does not need to implement any specific client extension - in fact, one doesn't have to use them at all. Simply enabling Client Extensions completely renders any project uncompilable.

Please see the sample repository to reproduce.

How to reproduce

Please see the sample repository with full reproduction instructions here: https://github.com/jove4015/special-umbrella.

In the repo, you will see a very simple application that inserts one record into a basic table with all scalar fields and no relationships. It runs correctly without client extensions enabled, but fails the second client extensions are turned on.

Expected behavior

With client extensions enabled, Prisma should still be able to resolve types properly and compile. Client Extensions should be usable.

Prisma information

Schema:

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model TestTable {
  id          Int     @id @default(autoincrement())
  task_name   String?
  status      String?
  description String?
  notes       String?
}

Prisma version 4.9.0

Environment & setup

  • OS: MacOS or Debian Linux running in Docker
  • Database: PostgreSQL
  • Node.js version: v16.17.1

Prisma Version

Environment variables loaded from .env
prisma                  : 4.9.0
@prisma/client          : 4.9.0
Current platform        : darwin-arm64
Query Engine (Node-API) : libquery-engine ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5 (at node_modules/@prisma/engines/libquery_engine-darwin-arm64.dylib.node)
Migration Engine        : migration-engine-cli ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5 (at node_modules/@prisma/engines/migration-engine-darwin-arm64)
Introspection Engine    : introspection-core ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5 (at node_modules/@prisma/engines/introspection-engine-darwin-arm64)
Format Binary           : prisma-fmt ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5 (at node_modules/@prisma/engines/prisma-fmt-darwin-arm64)
Format Wasm             : @prisma/prisma-fmt-wasm 4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5
Default Engines Hash    : ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5
Studio                  : 0.479.0
@millsp
Copy link
Member

millsp commented Feb 6, 2023

Thanks for your reproduction. I will take a look now.

@millsp
Copy link
Member

millsp commented Feb 6, 2023

So #17349, and #17563 are fixed. #16600 should not occur anymore. If you've hit this issue, please let me know.

@jove4015
Copy link
Author

jove4015 commented Feb 6, 2023

As far as I can tell, this error is just a more general version of what was referenced on #17563. I don't see how it's possible that that issue is fixed if even this one doesn't work.

@millsp
Copy link
Member

millsp commented Feb 6, 2023

const testTableQuery = {
  id: true,
  task_name: true,
};

const validated = Prisma.validator<Prisma.TestTableSelect>()(testTableQuery)

If you hover over validated, you'll see that they have not been narrowed to their original values. Instead of name being true, it is not narrowed but widened, it unwillingly became boolean. Internally, our types got a little bit stricter and are checking for true instead of boolean.

That internal change is more correct because boolean means true | false. That input type would suggests it needs a result both with included selection and without. We should return a union of an object with the selection and one without. That said, we returned never, which is also incorrect.

So all of this boils down to:

  1. Your types were not narrowed down properly
  2. We increased strictness but also got it wrong

So for you, everything will work once you can narrow types as they should have

const testTableQuery = Prisma.validator<Prisma.TestTableSelect>()({
  id: true,
  task_name: true,
});

const testFunction = async () => {

  const taskResult = await prisma.testTable.create({
    data: {
      task_name: "Testing",
    },
    select: testTableQuery,
  });
  console.log(taskResult.id.toString());
}

And for us it is to investigate what to do when input is both true | false. The old behavior of always including is incorrect, the new one is incorrect too. So we'll need to dig a bit deeper into that.

@millsp
Copy link
Member

millsp commented Feb 6, 2023

Also, this change does not seem to be influenced by extensions, but by the version. Does that match your understanding too?

@millsp millsp added bug/2-confirmed Bug has been reproduced and confirmed. topic: client types Types in Prisma Client tech/typescript Issue for tech TypeScript. team/client Issue for team Client. topic: clientExtensions labels Feb 6, 2023
@jove4015
Copy link
Author

jove4015 commented Feb 6, 2023

No - my code works fine on version 4.9.0 without the client extensions enabled, as it has on all the prior prisma versions as well. It's only with client extensions enabled that any of this breaks at all.

Also, I'm really not sure what you're getting at. As far as I can tell I am using this as documented here.

I am literally defining these as "true", not boolean. You can see however VSCode (correctly) indicating that true is in fact a boolean:

image

I'm not saying "boolean" anywhere so I don't see what else I would do differently. I'm also not saying false anywhere (the word is literally not in the codebase).

image

So - exactly what do you mean by narrowing and how exactly would I do that when I'm already using "true"?

@jove4015
Copy link
Author

jove4015 commented Feb 6, 2023

OK - after rereading a few times. I tried this and surprisingly it does seem to actually work:
image

However - this is obviously not how prisma is documented anywhere. Are we saying that now, in addition to defining the object, we have to hardcode a type indicating that every single one of these trues is actually just true and not a boolean? That would mean basically double-declaring each object since it's type signature would be equal to its definition.

It's important to note too - I can't even use what obstensibly should be the correct type of the select object (Prisma.TestTableSelect):

image

image

When I inspect Prisma.TestTableSelect, in fact, the type appears to expect a boolean, and not just the narrower "true":

image

@millsp
Copy link
Member

millsp commented Feb 6, 2023

Yes, although you did define these as true, TypeScript will widen them if you don't use our utility immediately (Also, fyi, you could as well use satisfies). So here is what you have:
Screenshot from 2023-02-06 10-08-37
And here is what you'd have by using the utility immediately:
Screenshot from 2023-02-06 10-08-57
As you can see, applying the utility afterwards does not preserve your initial input types. Basically our utility does domething similar as as const/statisfies for you and, similarly, it will need to be applied immediately to preserve full type information. If that wasn't clear enough in our docs, we should improve that.

@millsp
Copy link
Member

millsp commented Feb 6, 2023

This is for you to be unblocked right now. As I said above, we should not return never even if we do receive a boolean type.

@jove4015
Copy link
Author

jove4015 commented Feb 6, 2023

I mean, to me, it goes beyond not being documented to not being logical, ie:

a = {x: true, y: true};
f(a) === f({x:true, y:true})

I think most people would expect this to be true.

@millsp
Copy link
Member

millsp commented Feb 6, 2023

a = {x: true, y: true} TypeScript will infer that as {x: boolean, y: boolean}. That may not sound logic, but it's the way it is. Type-level information differs from runtime information and that is what Prisma will use to compute the types. Again, we will fix the never popping up, but boolean means it can be both true or false, in this case selected or not. That's the reason why I suggested you to use Prisma.validator as in the second picture because it will prevent true from becoming boolean. We will fix this, I was just giving you some background on the issue, while also giving you a solution.

@jove4015
Copy link
Author

jove4015 commented Feb 7, 2023

Thank you for taking the time to explain, I really do appreciate it. It took a while but I rooted out all the instances of this in our project and was finally able to get a build out of it. It's probably for the best; it sounds like our use of the validator wasn't having it's intended effect anyway.

@janpio
Copy link
Member

janpio commented Feb 8, 2023

All resolved @jove4015 then?

@jove4015
Copy link
Author

jove4015 commented Feb 8, 2023

Well - we're not blocked anymore, we just updated to use the validator the same exact way and also to use it for every single prisma call, and now we don't have type errors (on version 4.9). I think we're fine just continuing to work that way. So again thank you so much.

That being said, I went back to check on the demo repo and I upgraded it to 4.10. It did not seem to fix the problem there - I'm still seeing the same type error after generating and restarting typescript. I'm not sure if that's a big deal - the only thing that's really missing is some info in the documentation to indicate that the validator - used in a very specific way - or a narrowly declared type - is required when working with client extensions. Most examples don't use the validator, and you either have to use it or define all your queries inline (which at least for our project would be pretty impractical). In the absence of any other info to the contrary, I would otherwise expect that if you can write this:

const a = func({b:true, c:true});

you should also be able to express the same via the substitution property of equality:

const e = {b:true, c:true};
const d = func(e);

assert(d === a);

It's just not obvious that these limitations apply.

@CHelfgott
Copy link

CHelfgott commented Apr 20, 2023

I am running into what I think is a related issue on Prisma 4.10.1.
I have two branches of my GitHub repo. In one, my Prisma schema includes previewFeatures = ["clientExtensions"]; in the other it does not. That's the only difference -- in particular I have not implemented any extensions yet.

I have a deeply nested include/select:

export const FaxInclude = {
  include: {
    site: true,
    task: {
      include: {
        assignee: true,
        order: {
          select: {
            id: true,
            name: true,
            source: true
          },
        },
      },
    },
  },
}
const faxWithExtras = Prisma.validator<Prisma.FaxArgs>()(FaxInclude)
export type PrismaFax = Prisma.FaxGetPayload<typeof faxWithExtras>

The "site", "task", "assignee", "order", and "source" are all relations into other tables. The order id and name are fields in the Order table. Introspecting the PrismaFax type gives the expected ({site: Site | null; task: (Task & ...) | null}) for the non-extension branch, specifically with id: number and name: string. In the extension branch, id and name (and only id and name) are type never.

@CHelfgott
Copy link

Recording this for use by others: Replacing id: true and name: true with id: true as const and name: true as const fixes things, since Typescript never winds up widening "id" and "name" to boolean type. Why this only affects field-level selects and includes and does not affect relation-level selects and includes I do not know.

@Jolg42 Jolg42 changed the title With Client Extensions enabled, Typescript can no longer compute types Prisma.validator: with clientExtensions enabled, Typescript can no longer compute types May 24, 2023
@millsp millsp self-assigned this Jun 1, 2023
@janpio janpio added this to the 4.16.0 milestone Jun 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug/2-confirmed Bug has been reproduced and confirmed. kind/bug A reported bug. team/client Issue for team Client. tech/typescript Issue for tech TypeScript. topic: client types Types in Prisma Client topic: clientExtensions
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants