prisma-binding: Error: Cannot read property 'type' of undefined, when generating Typescript for array fields

Hi, I am having issues generating Typescript schemas for queries returning arrays. Following works with no issues:

type Query {
  notifications(start: Int, end: Int): Notification
}

following throws an error below:

type Query {
  notifications(start: Int, end: Int): [Notification]!
}

Error:

TypeError: Cannot read property 'type' of undefined
    at getWhere (/Users/tomi/Github/apps/corporatorts/node_modules/prisma-binding/dist/utils.js:44:9)
    at /Users/tomi/Github/apps/corporatorts/node_modules/prisma-binding/src/utils.ts:41:12
    at Array.map (<anonymous>)
    at getTypesAndWhere (/Users/tomi/Github/apps/corporatorts/node_modules/prisma-binding/src/utils.ts:38:21)
    at Object.getExistsTypes (/Users/tomi/Github/apps/corporatorts/node_modules/prisma-binding/src/utils.ts:9:17)
    at PrismaTypescriptGenerator.renderExists (/Users/tomi/Github/apps/corporatorts/node_modules/prisma-binding/src/PrismaTypescriptGenerator.ts:71:20)
    at PrismaTypescriptGenerator.render (/Users/tomi/Github/apps/corporatorts/node_modules/prisma-binding/src/PrismaTypescriptGenerator.ts:19:32)
    at /Users/tomi/Github/apps/corporatorts/node_modules/prisma-binding/src/bin.ts:69:34
    at step (/Users/tomi/Github/apps/corporatorts/node_modules/prisma-binding/dist/bin.js:33:23)
    at Object.next (/Users/tomi/Github/apps/corporatorts/node_modules/prisma-binding/dist/bin.js:14:53)

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Reactions: 5
  • Comments: 37 (2 by maintainers)

Commits related to this issue

Most upvoted comments

I cannot see it being resolved and I believe it might be the same issue. While running prisma generate I have been facing the error:

Cannot read property 'type' of undefined

Step by step I discovered that it is returned for type within only id field.

To work around this bug each type definition should have at least two fields.

I think I might understand what’s happening here. I was using the prisma-binding against my app, as well as against the prisma database. It works against prisma, but fails against my own app. For my own app, I’m now using the graphql-binding, you can check https://github.com/graphql-binding/graphql-binding-github for an example.

I’m new to prisma and this was not 100% clear from the docs. I think most people that are just getting started will have the same setup as I do; a prisma service and an “app”. Perhaps the codegen docs could more explicitly touch on this case, and outline that the prisma service needs prisma-binding, while your app needs the graphql-binding.

@Sharlaan , let me start with saying that you are right. Naming it PrismaMutation was not the best of the choices. I was planning to write a blog post about it, but had no time, so let me just do a quick intro to what is happening here.

The Goal

The goal is to have fully type safe resolvers of queries, mutations and types. It may seem like a lot of work to accomplish, but ultimately it is just a copy paste of one file, adjusting the import paths. Ultimately this is how it will look:

// the types below do all the heavy lifting of making everything type safe
import { Mutation, Notification, Query, Resolver } from './utils';

export const query: Query = {
  // params and ctx are type safe, parent and info are 'any'
  notifications(_parent, params, ctx, info) {} 
};

export const mutation: Mutation = {
  notify(_parent, args, ctx, info) {}
};

export const resolver: Resolver<Notification> = {
  // we provide two version of the solution, where type names in resolver are NOT type safe, 
  // or with a bit of extra work we can make it type safe
  Notification: {
    // parent, ctx is type safe, args and info is any
    text: async (parent, _args, ctx, info) => {}
  }
};

The benefit is obvious, just by stating Muation or Query as a type, the parent, context and arguments are automatically type safe, no need to define types for arguments. When schema is regenerated, it will automatically catch all errors.

The Problem

Graphql-Binding generates pretty awesome typescript definitions. Let’s look how it generates a resolver for query notifications

export interface Query {
    notifications: <T = Notification[]>(args: { start?: Int, end?: Int }, info?: GraphQLResolveInfo | string, options?: Options) => Promise<T> 
}

We can immediately see, that the headers of Apollo resolvers and generated GraphQL resolvers do not match.

// Apollo
resolver(parent, args, context, info)
// Generated
resolver(args, info, options)

The Solution

If you loved typescript, after reading following, hearing Typescript will play the song “Who Let The Dogs Out! Who!? Who!?” in your head … guaranteed. My solution is based on statically remapping the header parameters from generated resolver to match the Apollo resolver. For this, we will use the new infer keyword to infer the Type. Following line returns the type of the first parameter of function. I should reference here the site where I found this but I cannot remember 😭

export type FirstArgument<T> = T extends (arg1: infer U, ...args: any[]) => any ? U : any;

We are now ready to create new remapped Apollo resolver. The definition below specifies, that every member of the object of type T is a function with the defined header, notably moving the first parameter to second position. 🥇

export type Remapped<T> = {
  [P in keyof T]: (
    parent: null | undefined,
    args: FirstArgument<T[P]>,
    ctx: Context,
    info?: GraphQLResolveInfo
  ) => any
};

We are now ready to define the type safe version of resolvers of mutations and queries (type resolver will follow soon). We need to import the resolvers from API and we need to also import types from Prisma, to make it part of the context.

import { GraphQLResolveInfo } from 'graphql';

import { Mutation as ApiMutation, Query as ApiQuery } from './generated/api';
import { Prisma } from './generated/prisma';

export interface Context {
  db: Prisma;
  request: any;
}

export type FirstArgument<T> = T extends (arg1: infer U, ...args: any[]) => any ? U : any;

export type Remapped<T> = {
  [P in keyof T]: (
    parent: null | undefined,
    args: FirstArgument<T[P]>,
    ctx: Context,
    info?: GraphQLResolveInfo
  ) => any
};

// Following will make all your query and mutation resolvers type safe
export type Query = Partial<Remapped<ApiQuery>>;
export type Mutation = Partial<Remapped<ApiMutation>>;

Type Resolvers: Version A - Almost Type Safe

Type resolvers will become almost type safe using following generic definition. Note the this is the problem line. The problem is, that the name of the type is not watched and can be anything.

export type Resolver<T, U = any> = {
  [index: string]: { // this is a problem
    [P in keyof Partial<T>]: (parent: T, args: U, ctx: Context, info: GraphQLResolveInfo) => any
  }
};

Type Resolvers: Version B - Type Safe

Unfortunately to make this type safe we need to do a bit of extra work as Typescript’s in keyof does not work with namespaces. Therefore we need to manually define types that we will use in our resolvers. If you think this extra work is not worth it, just stick to version A.

// file: types.ts
import * as Api from './generated/api';

// export one variable of each type
export const Notifications: Api.Notifications;
export const User: Api.User;
import * as Types from './types';

export type Resolver<T> = {
  [U in keyof Partial<typeof Types>]: {
    [P in keyof Partial<T>]: (parent: T, args: any, ctx: Context, info: GraphQLResolveInfo) => any
  }
};

Wrap Up

That’s all folks! Pure profit. No need to manually define arguments, context, everything is sorted as easy as following:

import { Query } from './utils';

export const query: Query = {
   // hit cmd+space here and hear "who let the dogs out!"
}

Just for completeness, here is the copy-paste version of the solution

// file: utils.ts
import { GraphQLResolveInfo } from 'graphql';

import { Mutation as ApiMutation, Query as ApiQuery } from './generated/api';
import { Prisma } from './generated/prisma';
import * as Types from './types; // you can omit this

export type FirstArgument<T> = T extends (arg1: infer U, ...args: any[]) => any ? U : any;

export type Remapped<T> = {
  [P in keyof T]: (
    parent: null | undefined,
    args: FirstArgument<T[P]>,
    ctx: Context,
    info?: GraphQLResolveInfo
  ) => any
};

export interface Context {
  db: Prisma;
}

export type Query = Partial<Remapped<ApiQuery>>;
export type Mutation = Partial<Remapped<ApiMutation>>;
export type Resolver<T> = {
  [U in keyof Partial<typeof Types>]: { // or [index: string]: {
    [P in keyof Partial<T>]: (parent: T, args: any, ctx: Context, info: GraphQLResolveInfo) => any
  }
};

@mattferrin looks nice, I do not seem to have problems with your approach. It is working rock solid. Not sure why it’s failing for you 😭

Thanks @edorivai i totally missed this :

Perhaps the codegen docs could more explicitly touch on this case, and outline that the prisma service needs prisma-binding, while your app needs the graphql-binding.