Home > OS >  get a nested email field that is part of another model in prisma?
get a nested email field that is part of another model in prisma?

Time:08-10

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

CodePudding user response:

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

CodePudding user response:

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 :)

  • Related