prisma-binding
prisma-binding copied to clipboard
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)
What are the exact steps you follow? How can I reproduce this?
Hi.
Prisma.yml
endpoint: http://localhost:4466
datamodel:
- users.graphql
hooks:
post-deploy:
- graphql get-schema -p prisma
- graphql codegen
.graphqlconfig
projects:
app:
schemaPath: "src/data/yoga/schema.graphql"
includes: ["src/**/*.graphql"]
extensions:
endpoints:
default: "http://localhost:4000"
codegen:
- generator: prisma-binding
language: typescript
output:
binding: src/data/generated/api.ts
prisma:
schemaPath: "src/data/generated/prisma.graphql"
includes: ["src/**/*.graphql"]
extensions:
prisma: src/data/prisma/prisma.yml
codegen:
- generator: prisma-binding
language: typescript
output:
binding: src/data/generated/prisma.ts
users.graphql
type User {
id: ID! @unique
name: String!
roles: [String!]!
}
Now, as for YOGA
schema.graphql
#import User from '../generated/prisma.graphql'
type Query {
users: [User]!
}
Now I run prisma deploy
Running graphql get-schema -p prisma ✔
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)
Running graphql codegen ✖
Please share the following:
-
prisma version
- Prisma server version
-
graphql --version
- Node version
- OS
- prisma version: prisma/1.10.2 (darwin-x64) node-v10.2.1
- Prisma server version: ??? ... I use the docker image that was built yesterday (prismagraphql/prisma:1.11)
- graphql --version: 2.16.4
- Node version: 10.2.1
- OS: Mac High Sierra
[EDIT] I have just update Prisma to 1.11.1 and the error remains.
Having the exact same problem. My setup:
- prisma version: prisma/1.12.0 (linux-x64) node-v10.5.0
- Prisma server version: prismagraphql/prisma:1.12
- graphql --version: 2.16.4
- Node version: 10.5.0
- OS: Ubuntu 16.04
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
.
@edorivai Your solution appears to work, but takes me back to the initial issue I had using other techniques. I need to support graphql imports.
# import TeamConnection from "./generated/prisma.graphql"
Error: Type "TeamConnection" not found in document.
@edorivai Nevermind. Works perfectly using graphql-import
. I'm just a little slow this time.
src/schema.js
// for typescript types generation only
const { makeExecutableSchema } = require("graphql-tools");
const { importSchema } = require("graphql-import");
const typeDefs = importSchema(__dirname + "/schema.graphql");
const schema = makeExecutableSchema({
resolverValidationOptions: {
requireResolversForResolveType: false
},
typeDefs: typeDefs
});
module.exports = schema;
.graphqlconfig.yml
projects:
app:
schemaPath: src/schema.graphql
extensions:
endpoints:
default: http://localhost:4000
codegen:
- generator: graphql-binding
language: typescript
input:
schema: src/schema.js
output:
binding: src/generated/app.ts
database:
schemaPath: src/generated/prisma.graphql
extensions:
prisma: database/prisma.yml
codegen:
- generator: prisma-binding
language: typescript
output:
binding: src/generated/prisma.ts
@mattferrin how do you obtain src/schema.js
?
@tomitrescak You can create a new file name schema.js in your src directory. The copy and paste the top js code snippet into it. Should work. Let me know if you have issues.
Yeah .. I just found out studying graphql-binding ;) Thanks for a SUPER fast answer!!!
@tomitrescak And it looks like I'm going to use my schema.js
file with graphql-code-generator
instead too. Since my goal is to add static type checking to my server-side work and I can't take the Query, Mutation, and Subscription types generated and use them directly, graphql-code-generator
seems like it will generate more fine-grained types for me. (And I've had success with it client-side anyway.)
{
"scripts": {
"build-graphql":
"gql-gen --schema src/schema.js --template graphql-codegen-typescript-template --out ./src/generated/"
"start":
"npm run build-graphql && npm run tslint && dotenv -- nodemon -e ts,graphql -x ts-node src/index.ts",
},
"devDependencies": {
"graphql-code-generator": "^0.10.3",
"graphql-codegen-typescript-template": "^0.10.3"
}
}
@tomitrescak Looks like I was wrong. Neither generator works well to type my server-side. Not sure what the best approach is, but I'll work with what I got :)
@mattferin I like the generated content by GraphQL-binding. With a little typescript magic it gives me effortless completely type safe resolvers.
Here is magic
import * as Types from '../../prisma';
export * from '../../generated/prisma';
import { GraphQLResolveInfo } from 'graphql';
import { Mutation as PrismaMutation, Query as PrismaQuery } from '../../generated/api';
import { LanguageCode, Prisma, User } from '../../generated/prisma';
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 type Query = Partial<Remapped<PrismaQuery>>;
export type Mutation = Partial<Remapped<PrismaMutation>>;
export interface Context {
db: Prisma;
request: any;
session: {
user: User;
language: LanguageCode;
};
}
export type Resolver<T> = {
[U in keyof Partial<typeof Types>]: {
[P in keyof Partial<T>]: (parent: T, args: any, ctx: Context, info: GraphQLResolveInfo) => any
}
};
And this how it’s used, everything is type safe, arguments, context ...
import { getUserId, Mutation, Notification, Query, Resolver } from './utils';
export const query: Query = {
notifications(_parent, { start, end }, ctx, info) {
return ctx.db.query.notifications(
{ where: { owner: { id: getUserId(ctx) } }, skip: start, last: end },
info
);
}
};
export const mutation: Mutation = {
async notify(_parent, args, ctx, info) {
const user = await ctx.db.mutation.updateUser(
{
where: { id: args.userId },
data: {
notifications: {
create: {
code: args.code,
params: { set: args.params },
processInstance: {
connect: {
id: args.processInstanceId
}
},
visible: true
}
}
}
},
info
);
// return user.notifications[0];
// const notification = await ctx.db.query.notifications({ where: { }); // .user()
return true;
}
};
export const resolver: Resolver<Notification> = {
Notification: {
text: async (parent, _args, ctx, info) => {
const results = await ctx.db.query.localisations({
where: { code: parent.code, language: ctx.session.language }
});
return results[0];
}
}
};
@tomitrescak i don't understand some things in your strtategy :
- Differenciation API / Prisma(db)
import { Mutation as PrismaMutation, Query as PrismaQuery } from '../../generated/api';
would not it make more sense as :
import { Mutation as ApiMutation, Query as ApiQuery } from '../../generated/api';
since you are using those types for resolvers args and resolvers functions, while prisma function ytpes are already provided through Context.
So yea sound to me obvious both types matches since they are both from the generated prisma.ts, which isnot the point ?
- For example : api.schema.gql :
type Query {
order(id: ID!): Product
...
}
... and then i would find after generation that generated/api.ts exposes :
export interface Query {
order: <T = Order | null>(args: { where: OrderWhereUniqueInput }, info?: GraphQLResolveInfo | string, options?: Options) => Promise<T> ,
...
}
which is exactly the same as in generated/prisma.ts huh ? should not it reflect the api schema instead of prisma schema ?
From there, obviously any types based on generated/api.ts will match types from Context since they are the same ....
Unless i'm missing something ?
@tomitrescak Are the Types
exported from the prisma
package? My npm installed dependency has a blank type definitions file. Starting to clone prisma
, but it's a bit of work to figure out.
Guys, I’ll post full instructions tonight. Started to write them this morning but then my little one took over the day. Don’t waste time with the code above, there are a lot of assumptions there) I’ll post back in about 3-4 hours.
You're awesome. It looks pretty sweet. Thanks.
I haven't figured out how I broke the database playground yet, but the below was sufficient to get sweet type checking:
import { GraphQLResolveInfo } from "graphql";
import * as jwt from "jsonwebtoken";
import {
Mutation as AppMutation,
Query as AppQuery,
Subscription as AppSubscription
} from "./generated/app";
import { Prisma } from "./generated/prisma";
type FirstArgument<T> = T extends (arg1: infer U, ...args: any[]) => any
? U
: any;
type Remapped<T> = {
[P in keyof T]: (
parent: null | undefined,
args: FirstArgument<T[P]>,
ctx: IContext,
info?: GraphQLResolveInfo
) => any
};
type SubscriptionRemapped<T> = {
[P in keyof T]: {
subscribe: (
parent: null | undefined,
args: FirstArgument<T[P]>,
ctx: IContext,
info?: GraphQLResolveInfo
) => any;
}
};
export type Query = Remapped<AppQuery>;
export type Mutation = Remapped<AppMutation>;
export type Subscription = SubscriptionRemapped<AppSubscription>;
export interface IContext {
db: Prisma;
request: any;
}
@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
}
};
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.
back to error ' Cannot read property 'type' of undefined" I have found https://github.com/prisma/prisma-binding/issues/192#issuecomment-401418309 So I have add 'fake param' where
If you modify your buggyTypes query to include some arguments, it will resolve your issue. For example:
type Query { buggyTypes(where: ID!): [BuggyType!]! }
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.
@tomitrescak
export type Remapped<T> = {
[P in keyof T]: (
parent: null | undefined,
args: FirstArgument<T[P]>,
ctx: Context,
info?: GraphQLResolveInfo
) => any
};
Have you found a way to add types to the return value. Using any
has not been pleasant for me.
@mattferrin I would have no idea where to start as each resolver returns it's own thing.
@tomitrescak I'm a little confused honestly, but I think the below has compiled without issues at times, but at other times has failed with ReturnType<T>
being inferred as {}
instead. Then I have to revert back to any
despite only a few properties erring.
type ReturnTypeInfer<T> = T extends (...args: any[]) => any
? ReturnType<T> // doesn't seem to work reliably, typescript static checking seems unpredictable
: any;
type Remapped<T> = {
[P in keyof T]: (
parent: null | undefined,
args: FirstArgument<T[P]>,
ctx: IContext,
info?: GraphQLResolveInfo
) => ReturnTypeInfer<T[P]>
};
I appreciate the response back regardless. Thanks.
@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 ;(
What are the exact steps you follow? How can I reproduce this?
I used extended schema and add some type without arg where
type Query {
getMyTypes: [MyType!]!
}
And got this error.
I just add where condition and all OK
type Query {
getMyTypes(
where: MyCond
): [MyType!]!
}
Error became when i create server directly without declare resolvers (I use them like as proxy);
api = new Prisma({
typeDefs: 'src/schema/generated/api.graphql',
endpoint: 'http://localhost:4000',
secret: 'mysecret123',
debug: false,
});
UPD: It's actual for list selections, results like usersOnline: [User!]!
usersOnline(where: UserWhereInput): [User!]! Works fine.
@edorivai
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 thegraphql-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 thegraphql-binding
.
I am new to Prisma, and this was the source of my problem. The docs are not clear on this, and I suspect it may confuse a lot of people. Thanks for clearing that up.