prisma icon indicating copy to clipboard operation
prisma copied to clipboard

fix(client): type inference for `prismaClient.$on(...)`

Open nikelborm opened this issue 9 months ago • 4 comments

Problem

  1. incorrect inference in generic GetLogType returned never instead of T on line 6. (original piece of code here)
/*1.*/export type GetLogType<T extends LogLevel | LogDefinition> =
/*2.*/  T extends LogDefinition
/*3.*/    ? T['emit'] extends 'event'
/*4.*/      ? T['level']
/*5.*/      : never
/*6.*/  : never

This code led to error when some of the elements in T argument of GetEvents which are being later passed to GetLogType were LogLevel string literals instead of LogDefinition objects.

  1. Case when developer forgot to add as const (and there's no suggestion in documentation to add it) to the argument of PrismaClient constructor.

Example in documentation:

const prisma = new PrismaClient({
  log: ['query', 'info', 'warn', 'error'],
})

Since neither developer, nor PrismaClient generic itself (const before U is absent here) didn't tell typescript to treat argument of new PrismaClient(...) as constant, the type information of string literals will be erased and typescript will handle it just as { log: string[] }.

  1. GetEvents was hardcoded to expect arrays with length at max 4-elements here

Solution

  1. GetLogType now returns those literals as is when they don't extend LogDefinition (object), but also with additional check which then forces T of GetLogType<T> to extend LogLevel
  2. Added useful const modifier to the second U argument of PrismaClient generic which preserves string literal type info taken from the argument of PrismaClient constructor
  3. GetEvents generic is now a recursive type and doesn't depend on length of PrismaClientOptions.log array

Might fix some of the following issues:

  1. https://github.com/prisma/prisma/issues/19463
  2. https://github.com/prisma/docs/issues/5021
  3. https://github.com/prisma/docs/issues/1564
  4. https://github.com/prisma/prisma/issues/4746
  5. https://github.com/prisma/prisma/issues/7068
  6. https://github.com/notiz-dev/nestjs-prisma/issues/56
  7. https://github.com/prisma/prisma/issues/11986
  8. https://github.com/prisma/prisma/issues/6279
  9. https://github.com/prisma/docs/issues/3424

Before

U argument of PrismaClient generic is never mainly because of the 1st problem mentioned above and we can't call prismaClient.$on(...) passing any string literal to it: Screenshot from 2024-03-05 14-23-28

After

The 1st screenshot shows that the second inferred argument U of generic PrismaClient stored in DB constant is now "info" | "query" | "error" according to IDE's tooltip above the line DB.$on('', () => {}) and typescript also tells to replace first argument '' to any of U union members: Screenshot from 2024-03-05 14-37-44

2nd screenshot shows that when we pass correct 1st argument to DB.$on it correctly infers type of the 2nd function argument: Screenshot from 2024-03-05 14-41-01

nikelborm avatar May 08 '24 22:05 nikelborm

CodSpeed Performance Report

Merging #24133 will not alter performance

Comparing nikelborm:main (06709d7) with main (7aae7ec)

Summary

✅ 3 untouched benchmarks

codspeed-hq[bot] avatar May 08 '24 23:05 codspeed-hq[bot]

CLA assistant check
All committers have signed the CLA.

CLAassistant avatar Jul 08 '24 23:07 CLAassistant

Any updates or progress on this? Are we able to get this merged? Running into this issue when attempting to customise the prismaClient event logging.

kduprey avatar Oct 01 '24 22:10 kduprey

Any updates or progress on this? Are we able to get this merged? Running into this issue when attempting to customise the prismaClient event logging.

I honestly have no idea why this wasn't merged. I came back here few times already to pull upstream, patiently waiting for a response or review. Haven't got any in almost half of the year.

nikelborm avatar Oct 02 '24 21:10 nikelborm