1

i have schema that looks like:

schema.prisma

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

datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

model User {
  id       String  @id @default(cuid())
  email    String? @unique
  stripeId String  @unique

  createdAt DateTime @default(now())

  product Product?

  @@map("users")
}

model Product {
  id       String @id @default(cuid())
  totalSum Int    @default(9700)

  user   User   @relation(fields: [userId], references: [id])
  userId String @unique

  licenses License[]

  @@map("products")
}

model License {
  id    String @id @default(cuid())
  name  String @unique
  /// no. of licenses generated
  total Int    @default(1)
  /// no. of licenses used
  used  Int    @default(0)
  /// stored in cents

  product   Product? @relation(fields: [productId], references: [id])
  productId String?

  createdAt DateTime @default(now())

  @@map("licenses")
}

i want to access email field while doing prisma.license.findMany(). my db file looks like:

db.ts

import { Prisma, License, User } from '@prisma/client'

import { prisma } from './context'

export const getLicenses = async (): Promise<
  Array<License & Pick<User, 'email'>> | null | undefined
> => {
  const userSelect = Prisma.validator<Prisma.ProductSelect>()({
    user: {
      select: {
        email: true,
      },
    },
  })

  const productSelect = Prisma.validator<Prisma.LicenseSelect>()({
    product: {
      include: userSelect,
    },
  })

  const licenses = await prisma.license.findMany({
    orderBy: {
      createdAt: 'desc',
    },
    include: productSelect,
  })

  const result = licenses.map((license) => {
    const email = license.product?.user.email

    if (email) {
      return {
        ...license,
        email,
      }
    }
  })

  return result
}

export const db = {
  getLicenses,
}

the last line return result gives this typescript error:

Type '({ email: string; id: string; name: string; total: number; used: number; productId: string | null; createdAt: Date; product: (Product & { user: { email: string | null; }; }) | null; } | undefined)[]' is not assignable to type '(License & Pick<User, "email">)[]'.
  Type '{ email: string; id: string; name: string; total: number; used: number; productId: string | null; createdAt: Date; product: (Product & { user: { email: string | null; }; }) | null; } | undefined' is not assignable to type 'License & Pick<User, "email">'.
    Type 'undefined' is not assignable to type 'License & Pick<User, "email">'.
      Type 'undefined' is not assignable to type 'License'.ts(2322)

my schema file looks like:

schema.ts

import { db } from './db'

const Query = objectType({
  name: 'Query',
  definition(t) {
    t.list.field('licenses', {
      type: 'License',
      resolve: async (_, __, ctx) => {
        if (!ctx.admin.isLoggedIn) return null
        const licenses = await db.getLicenses()

        if (licenses) return licenses
        return null
      },
    })
  },
})

even this little query is causing me a lot of errors. it used to work when i wanted to query all of licenses using prisma.license.findMany() but it started throwing errors as soon as i wanted email field but in a flat file format so my output looks like:

{
    id: string;
    name: string;
    total: number;
    used: number;
    productId: string | null;
    createdAt: Date;
    email: string;
}

i also don't want productId to be sent. how can i solve this?

i've made a minimal repro → https://github.com/deadcoder0904/prisma-nested-query-email

deadcoder0904
  • 7,232
  • 12
  • 66
  • 163

2 Answers2

0

When you use Array.map, if you don't explicitly specify the return for every element, then undefined will make its way into your array - which is what your TS error is indicating. If (!email), then map returns an undefined element to the output array.

Essentially, what you want is to filter those licenses that have an associated user with an email, and THEN map it.

You can actually do this with reduce instead. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce#replace_.filter.map_with_.reduce

You're trying to use Array.map LIKE this Array.reduce functionality (which can replace filter/map). But undefined can't sneak into the returned array if you implement with Array.reduce.

Here's a relevant CodeSandbox link. Note that eslint is complaining (expected to return a value at the end of arrow function). https://codesandbox.io/s/nifty-surf-s8vcg6?file=/src/index.ts

Michael Jay
  • 503
  • 3
  • 15
  • the codesandbox link is wrong bdw so can't see the code. i can't see that i'm using `filter` anywhere but just `map`. how do i replace it with `reduce`? – deadcoder0904 Aug 09 '22 at 13:19
  • Sorry - I didn't save my changes in the CodeSandbox. Should be good now. – Michael Jay Aug 09 '22 at 13:37
  • And the way that you're quasi-using filter is that (assumedly) you're trying to ONLY map those elements that have emails. But map doesn't work that way by itself. – Michael Jay Aug 09 '22 at 13:38
  • thanks michael, you're right i fixed that `map` part & found a solution already [in this commit](https://github.com/deadcoder0904/prisma-nested-query-email/commit/16023c3126f024207d9116067febd158a0dca09f). had to disable ts & check the results & then had to figure out that `email: null` had to be sent so removed the `if` statement :) – deadcoder0904 Aug 09 '22 at 13:40
-1

I disabled TS on the db.ts file & then checked results array & figured out I needed to have email as null instead of undefined.

One important note: I had to stop using nonNull for email in nexus configuration of type License to stop the error.

The working solution is posted in this commit. It works now :)

deadcoder0904
  • 7,232
  • 12
  • 66
  • 163