Code Monkey home page Code Monkey logo

graphql-binding's Introduction

graphql-binding

Deprecation Notice!

In the last few months, since the transition of many libraries under The Guild's leadership, We've reviewed and released many improvements and versions to graphql-cli, graphql-config and graphql-import.

We've reviewed graphql-binding, had many meetings with current users and engaged the community also through the roadmap issue.

What we've found is that the new GraphQL Mesh library is covering not only all the current capabilities of GraphQL Binding, but also the future ideas that were introduced in the original GraphQL Binding blog post and haven't come to life yet.

And the best thing - GraphQL Mesh gives you all those capabilities, even if your source is not a GraphQL service at all!
it can be GraphQL, OpenAPI/Swagger, gRPC, SQL or any other source! And of course you can even merge all those sources into a single SDK.

Just like GraphQL Binding, you get a fully typed SDK (thanks to the protocols SDKs and the GraphQL Code Generator), but from any source, and that SDK can run anywhere, as a connector or as a full blown gateway. And you can share your own "Mesh Modules" (which you would probably call "your own binding") and our community already created many of those! Also, we decided to simply expose regular GraphQL, so you can choose how to consume it using all the awesome fluent client SDKs out there.

If you think that we've missed anything from GraphQL Binding that is not supported in a better way in GraphQL Mesh, please let us know!

Overview

๐Ÿ”— GraphQL bindings are modular building blocks that allow to embed existing GraphQL APIs into your own GraphQL server. Think about it as turning (parts of) GraphQL APIs into reusable LEGO building blocks. Bindings may be generated in JavaScript, TypeScript, or Flow.

The idea of GraphQL bindings is introduced in detail in this blog post: Reusing & Composing GraphQL APIs with GraphQL Bindings

Install

yarn add graphql-binding

Public GraphQL bindings

You can find practical, production-ready examples here:

Note: If you've created your own GraphQL binding based on this package, please add it to this list via a PR ๐Ÿ™Œ

If you have any questions, share some ideas or just want to chat about GraphQL bindings, join the #graphql-bindings channel in our Slack.

graphql-binding's People

Contributors

abhiaiyer91 avatar aetherall avatar brikou avatar brunoscheufler avatar danrasmuson avatar dotansimha avatar kbrandwijk avatar lazaruslarue avatar maticzav avatar mpacholec avatar nikolasburk avatar onive avatar pradel avatar renovate-bot avatar renovate[bot] avatar schickling avatar shalkam avatar skovhus avatar spacek33z avatar timsuchanek avatar tjpeden avatar urigo avatar xbeg9 avatar yaacovcr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphql-binding's Issues

console.log(binding) throws error [depends on nodejs/node#10731]

  1. Github Binding example doesnt run: GraphQLError: Syntax Error: Unexpected Name "GitObject"
  2. Creating the minimal example throws:
    Cannot convert a Symbol value to a string
  3. Adding a binding to info.test.ts to assert properties gets:
    Cannot convert a Symbol value to a string

Seems like this module doesnt actually work?

Error : cannot convert a symbol value to string

I have tried to execute the example given in readme, but getting the following error
cannot convert a symbol value to string

const { makeExecutableSchema } = require('graphql-tools')
const { Binding } = require('graphql-binding')

const users = [
  {
    name: 'Alice',
  },
  {
    name: 'Bob',
  },
]

const typeDefs = `
  type Query {
    findUser(name: String!): User
  }
  type User {
    name: String!
  }
`

const resolvers = {
  Query: {
    findUser: (parent, { name }) => users.find(u => u.name === name),
  },
}

const schema = makeExecutableSchema({ typeDefs, resolvers })

const findUserBinding = new Binding({
  schema
})
 console.log(findUserBinding)
 findUserBinding.findUser({ name: 'Bob' })
  .then(result => console.log(result))
 
  
  export default schema

GraphQL Bindings for front-end usage (compatible with any GraphQL Client)

Hohai people,

The most powerful feature from bindings, IMO, are the TypeScript static typings provided with them. It almost completely removes the need for a GraphQL IDE (such as GraphiQL or Playground) when developing your api, increasing a lot your productivity.

Now, bindings can already be used for both the backend and the frontend, but at a cost: It prevents you from using a GraphQL Client (eg: Apollo).

The main reason behind this problem is that GraphQL Bindinds not only generate the query, but also send the actual request to the endpoint.

Here's my thought about it: Why don't we create another sort of binding, that would only generate graphql queries, so that we can let any GraphQL client handle the rest ? (The rest being things like caching, state-management, framework integration etc, etc..).

Here's what I mean. Let's say you want to query your graphql endpoint using those new bindings:

You'd first generate your query:

const ONLINE_USERS = db.query.users({ where: { online: true } });

Which would basically be equal to something like this:

const ONLINE_USERS = gql`{
   users(where: { online: true }) {
      ...
   }
}`

Then, you could use any client you want, such as Apollo, and connect your query to your component (a React component here, but that could be anything):

 export graphql(ONLINE_USERS)(MyComponent)

Potential flaws

There's lots of things that should be discussed, here's two of them for example:

  • What kind of api do we generate to let the devs choose what field they want to query ?

    Something like this for example: ( Note that we would loose static typings using this ugly API.)

    const ONLINE_USERS = db.query.users({ where: { online: true } }, ['id', 'firstname', 'lastname']);
  • How do we allow nested queries ?

So far, the main cons regarding this idea are the followings:

  • Queries are gonna get harder to read, especially with huge nested ones
  • Queries are gonna get harder to debug
  • The lost for any kind of static analysis for your queries

The more I think about it, the more I realize that might not be such a good idea.
It might be worth debating about it however, so let's use this thread to gather ideas if you guys see any potential !

Cheers ๐Ÿป

No license file

Currently there is no license file in the repo, meaning that the work is by default under exclusive copyright. [source]

Please add the appropriate license to open up the work for permitted usage.

Otherwise, if you purposefully left out a license in order to retain exclusivity, please add a note to the README saying so.

Generate GraphQL bindings from REST APIs

It should be possible to transform any REST API into a GraphQL binding, based on some kind of structured API documentation, such as Swagger / Open API docs.

Open questions:

  • What kind of REST API tooling provides/generates a structured documentation that could be used as a foundation to generate the GraphQL schema?

Limitations of `makeSubInfo`/ info field selection

The info object used in resolvers includes the sub-selection of the query for the current field.
As described here prisma-labs/prisma-binding#118, there are valid use-cases for extracting subfields from this selection to pass that into a binding.
In #118 and also this PR #69, an approach is presented that tries to solve this issue.

In the following I describe how this could be used and discuss what the fundamental limitation of this approach is, that we need to understand to move forward with this topic.

Let's say we have the following data model:
datamodel.graphql

type User {
  id: ID! @unique
  name: String!
}

type Address {
  id: ID! @unique
  street: String!
  userId: String!
}

In this case, for some reason we couldn't create a direct relation between both types, maybe because they're living in separate sources.
The schema that we actually want to expose is this:

type Query {
  me: User
}

type User {
  id: ID! @unique
  addresses(skip: Int first: Int): [Address!]!
}

type Address {
  id: ID! @unique
  street: String!
}

Now we want to establish this connection between types using graphql-yoga & graphql-binding.

The current approach without the new tooling would look like this:

{
  Query: {
    me(parent, args, ctx, info) {
      const userId = getUserId(ctx)
      return ctx.db.query.user({where: {id: userId}}, info)
    }
  },
  User: {
    addresses: {
      fragment: `fragment UserId on User { id }`,
      resolve(parent, args, ctx, info) {
        return ctx.binding.query.addresses({where: {userId: parent.id}}, info)
      }
    }
  }
}

As described in #118, it can be more efficient (query batching / saving queries) to combine this into one resolver.
Now makeSubInfo comes into play.
The API of makeSubInfo looks like this:

makeSubInfo(info: GraphQLResolveInfo, path: string, fragment?: string): GraphQLResolveInfo

We now can use that util function for our example:

{
  Query: {
    me(parent, args, ctx, info) {
      const userId = getUserId(ctx)
      const subInfo= makeSubInfo(info, 'address')
      const addresses = ctx.db.query.addresses({where: {userId}}, subInfo)
      return {
        id: userId,
        addresses
      }
    }
  },
}

For queries, that look like this, it's working as expected:

{
  me {
    id
    addresses {
      street
    }
  }
}

Problem

However, the following queries will break this resolver:

1. Not selecting the sub field

{
  me {
    id
  }
}

Here addresses are not selected at all, so the delegation to graphql binding will fail.

2. Using aliases in the query

{
  me {
    id
    first5: address(skip: 0, first: 5) {
      street
    }
    next5: address(skip: 5, first: 5) {
      street
    }
  }
}

Here we're using multiple aliases for the same field, but different args, so their respond has to differ.
The current implementation would just return an object of this form:

{
  me: {
    id: '',
    addresses: [],
  }
}

To actually provide the correct values for each aliased field, the returned object has to look like this:

{
  me: {
    id: '',
    first5: [],
    next5: []
  }
}

This means we have to either sacrifice the use of aliases or adjust the API of makeSubInfo.

Solution

The sacrifice of just not using aliases in queries is trivial, we don't have to further discuss that.

But let's say we still want to support aliases in our queries. This would mean, that we need to keep the output of the delegation to the binding with makeSubInfo dynamic.

One solution could be, that instead of directly returning the resolved value, we would have to wrap it in an object like this:

const query = `{ me { addresses { street } } }`
const result = binding.query.addresses({}, makeSubInfo(info, 'addresses'))

assert(result).toBe({
  addresses: []
})

or in the case of a query containing aliases:

const query = `{ me { addresses1 { street } addresses2 { street } } }`
const result = binding.query.addresses({}, makeSubInfo(info, 'addresses'))

assert(result).toBe({
  addresses1: [],
  addresses2: [],
})

This, however, would make typings on these bindings useless, as the response could look like anything. This is a trade-off we have to live with in this case.

To keep the nice typings and still have with flexible API, the proposal is to separate these into 2 APIs:

binding.delegateQuery.addresses({}, info)
binding.query.addresses({}, '{ id }')

The .delegateQuery method would return the type any as typings are of no value here, the .query method would return a type that is being generated based on the selection set in the last argument of the method, the fragment.

This is just a rough proposal for the future API, open for discussion.

Anyone interested working on this topic, please respond here or join our public Graphcool slack to join the conversation!

Access error object when delegating a mutation through prisma-binding

Hey! ๐Ÿ‘‹

So, the Prisma API now returns error codes for errors, like when violating the unique constraint. But, when delegating a mutation through prisma-binding, the error code is part of a string error.message which is not machine-readable. I've also found error.originalError, but same story. How do I access the error code? ๐Ÿ˜›

Here's some more context:

try {
    const user = await context.prisma.mutation.createUser({ data });
} catch (error) {
    console.log(error);
}

If there is a violation of the unique constraint for email, this is what is logged:

Error: A unique constraint would be violated on User. Details: Field name = email: {"response":
{"data":null,"errors":[{"locations":[{"line":2,"column":3}],"path":
["createUser"],"code":3010,"message":"A unique constraint would be violated on User. Details: 
Field name =email","requestId":"api:api:cjdal8pwd0ep30180fc4r3tfk"}],"status":200},"request":
{"query":"mutation ($_data: UserCreateInput!) {\n createUser(data: $_data) {\n id\n name\n email\n 
}\n}\n","variables":{"_data":{"name":"name","email":"email"}}}}

This string can also be accessed through error.message and error.originalError.message. The error code 3010 is in there, but how do we access it? Thereโ€™s no error object to access it from.

Here's an image with the properties of the caught error:

error-properties

Also, as per @marktani's suggestion, I tried to not catch the error, and console.log(user). We get the same string.

Thanks! ๐Ÿ™

Enhancement: Support remote schema stitching/execution over other transports (e.g. gRPC)

Posting as a possible enhancement per conversation with @schickling on the Slack channel:

Remote schema stitching and execution requires every remote service run as a GraphQL Server exposed over a HTTP-based API. In a microservices-based architecture where those remote services are only involved in service-to-service communication, leveraging HTTP as the transport introduces a fair amount of unnecessary overhead. Consider adding a way to leverage other back-end efficient transport protocols to communicate between the root GraphQL API server and the remote services.

Context should be parameter for generated delegates

Some delegate queries need access to the context, which is currently not passed in. This needs to be added as parameter.

Adding it as last parameter is non-breaking, but also non-standard. Adding it before info requires you to specify context. Both are not great.

Alternatively, we should switch to named parameters, which is also breaking.

@schickling thoughts?

Use of generic type in codegen for typescript?

The unrestricted generic type generated is causing some trouble to here. For example in this generated definition:

export interface Query {
  me: <T = User | null>(
    args?: {},
    info?: GraphQLResolveInfo | string,
    options?: Options
  ) => Promise<T>;
}

T is unrestricted and therefore it's effectively equivalent to T extends any = User | null, even it has a default. In other words, the typing definition is pointless here. You can check this by ReturnType<Query['me']> and find this the resulting type is Promise<{}> rather than the desired Promise<Partial<User> | null>.

I understand that the code above is generated from renderMainMethodFields as follow.

https://github.com/graphql-binding/graphql-binding/blob/e7bfcfde192b4c0304f35fd875485f55134a8120/src/codegen/TypescriptGenerator.ts#L240-L270

To fix the issue, we can just generate a simple return statement, e.g.

export interface Query {
  me: (
    args?: {},
    info?: GraphQLResolveInfo | string,
    options?: Options
  ) => Promise<Partial<User> | null>;
}

@timsuchanek: Are you aware of any other potential issues, besides the need of change in getPayloadType, if I make a PR to make a change to get rid of the generic type?

Nested fields input arguments are ignored

My schema allows login like:

mutation {
  auth {
    login(input: { email: "aaa", password: "aaa" }) {
      token
    }
  }
}

However, generated binding is ignoring input argument for login field. It is only allowing arguments on root mutation and query field and that is pretty much making it useless for my use-case.

My config:

{
  "projects":{
    "XYZ":{
      "schemaPath":"src/schema/api.graphql",
      "extensions":{
        "endpoints": {
          "dev":"http://localhost:9000/api/"
        },
        "prepare-binding": {
          "output": "src/schema/api.ts",
          "generator": "binding-ts"
        }
      }
    }
  }
}

I'd like to use binding like

binding.mutation.auth.login(inputDataHere)

or as async

const auth = await binding.mutation.auth();
auth.login(inputDataHere)`;

// or even (await binding.mutation.auth()).login(data)

Make going from schema to remoteExecutableSchema easier

graphql-binding requires a remoteExecutableSchema. It would be nice if it provided a convenience method (much like prisma-binding does), for creating a standard remoteExecutableSchema using apollo-link-http and makeRemoteExectubleSchema.

Scalar root field (string) only returns undefined

What is the expected return value of a generated function on query when the root field is a scalar?

Consider this example:

const { makeExecutableSchema } = require('graphql-tools')
const { Binding } = require('graphql-binding')

const typeDefs = `
  type Query {
    hello(name: String): String
  }
`

const resolvers = {
  Query: {
    hello: (parent, { name }) => `Hello ${name || 'World'}`,
  },
}

const schema = makeExecutableSchema({ typeDefs, resolvers })

const helloWorldBinding = new Binding({
  schema,
})

helloWorldBinding.query.hello().then(result => console.log(result))

With the current implementation, it prints only undefined.

I'd expect this to print either Hello World or { hello : 'Hello World' }.

Implement 'addMissingResolvers' feature

It would be nice if there was a addMissingResolvers(bindingName) function that would add forwardTo resolvers for any root field resolvers that are not implemented manually.

Use case. You import a (large) number of queries and mutations from a backend schema, and want forwardTo resolvers for most of them.

graphql-bindings 2.0 and executable schema

Running: graphql-binding -l typescript --input src/schema/appSchema.graphql --outputBinding appB Binding.ts

Throws: Error: graphql-bindings 2.0 can only be generated based on an executable Schema exposed by a .js or .ts file

What is the proper way to expose the executable schema? Through this script here .. https://github.com/graphql-binding/graphql-binding/blob/master/examples/full-example/schema.js ?

Would appreciate an explanation about the rationale behind this step... since we are running graphql-binding to generate the bindings shouldn't it just take the raw sdl and opaquely generate the executable schema?

Embed info object into a path

Consider the following situation:

# schema.graphql
Query {
  getA: A
}

type A {
  id: ID!
}
# our query
query {
  getA {
    id
  }
}
# remote.graphql
Query {
  getB: B
}

type B {
  a: A
}

type A {
  id: ID!
}
// resolver
getA(parent, args, ctx, info) {
  // this doesn't work, because we return `B` but query for `A`
  return ctx.remote.query.getB({ }, info)

  // this works, but doesn't use `info`. for more complicated queries, this becomes unmaintainable.
  const b = await ctx.remote.query.getB({ }, `{ a { id } }`)
  const a = b.a
  return { ...a }

  // ideally, we could "embed" the `info` object into a query
  return ctx.remote.query.getB({ }, embed(info, `{ a { $info } }`)
},

Visual explanation.

We want to get from

.
โ””โ”€โ”€ getA
 ย ย  โ”œโ”€โ”€ id
ย ย   โ””โ”€โ”€ anotherField

to

.
โ””โ”€โ”€ getB
 ย ย  โ””โ”€โ”€ a
  ย ย  ย ย  โ”œโ”€โ”€ id
 ย ย   ย ย  โ””โ”€โ”€ anotherField

args are being overridden when name clashes

For example, when both apis have a single argument called data, then the provided data is being overridden by the argument in the original request:

ctx.db.mutation.createClusterProvisionRequest(
      {
        data: {
          message: 'This will not be in the data arg sent to the server',
        },
      },
      info
    )

A workaround is to not include info in the delegated request.

DELETED events ignored on subscription

In my project, DELETED events are not showing up in my custom endpoint (passed via graphql-binding, but they are firing on the prisma backend.

For reference, here's my resolver:

export const user = {
  subscribe: (parent, args, ctx: Context, info) => {
    return ctx.db.subscription.user(args, info)
  },
}

And the subscription:

subscription UserChange {
  change: user {
    mutation
    node {
      id
      name
      #...
    }
  }
}

Using the same subscription directly on Prisma works as expected.

Using Bindings with Fragments for a Stitched Schema

My questions are how to use fragments with bindings and how to use Dataloader with bindings.

To preface, I may be completely lost in my own head on this problem. So let me give the 100,000ft view and hopefully there's an answer.

I have two servers, one for vehicles and one for files. The vehicles server needs to stitch in the file server. The vehicle server wants to query:

{
   vehicles {
         year
         make
         ....
         heroImages {
               url
         }
}

heroImages comes from the file server. Here's how I've implemented this so far.

I used get-schema to save a copy of the file server to the vehicle server. I imported the needed types through schema.graphql.

My app schema:

  const appSchema = makeExecutableSchema({
    typeDefs: [
      importSchema(`./src/schema.graphql`),
      `extend type Vehicle { heroImages: [FileGroup!]! }`
    ],
    resolvers
  })

Then I build the binding for the file server

const makeFileServiceLink = new HttpLink({
  uri: `url`,
  fetch
})

const remoteSchema = makeRemoteExecutableSchema({
    schema: await introspectSchema(makeFileServiceLink),
    link: makeFileServiceLink
})

const fileServer = new Binding({
    schema: remoteSchema,
    fragmentReplacements: {}
})

All good so far. But here's where things go sideways. Obviously heroImages is an N+1 call. So I made a Dataloader...

  const heroImagesLoader = new DataLoader(async ids => {
    const images = await fileServer.query.vehicleHeroImages(
      { input: { vehicleIds: ids } },
    )
    return ids.map(id => {
      const FileGroup = images.find(({ groupId }) => groupId === id)
      return FileGroup ? [FileGroup] : []
    })
  })

And the resolver...

{
  Vehicle: {
     heroImages: {
        fragment: `fragment VehicleFrag on Vehicle { stockNo }`,
        resolve: async ({ stockNo }, source, ctx, info) => {
          const { heroImagesLoader } = ctx.loaders
           return heroImagesLoader.load(stockNo)
       }
    }
  }
}

The first few problems rear their heads.

  1. How to get info into the binding query. DataLoader's API is pretty strict. I've made it work by turning it into an object, but whoa hack...
    heroImagesLoader.load({ id: stockNo, info}) and then mapping the ids and passing ids[0].info for the info binding parameter.

  2. How to get a field into the binding if it's not queried. In my set up, the vehicle server stores a stockNo for each vehicle. The file server groups files by a groupId. For vehicles, the stockNo is the groupId. Therefore, I cannot map the results back without knowing the groupId field. I'm sure there's a hack I could do to info, but isn't there a better way?

I tried messing with the binding's fragmentReplacement to no avail. e.g.

 const fragmentReplacements = extractFragmentReplacements({
    Query: {
      vehicleHeroImages: {
        fragment: `fragment GroupId on FileGroup { groupId }`
      }
    }
  })

 const imageServer = new Binding({
    schema: remoteSchema,
    fragmentReplacements
  })

to no avail.

Any help is a huge help! Thanks everyone as always for your time and effort!

Implement @forwardTo directive

We already have a forwardTo utility helper function in graphql-binding.
A more convenient way to specify the forwarding would be in the schema itself.
It could look like this:

type Query {
  ourUsers: [User!]! @forwardTo(ctx: "db" query: "users")
}

Before starting to implement something like that, it is important to decide where this logic should live in the ecosystem. While graphql-yoga would have the context to provide this feature, the semantic correct place to implement it is graphql-binding, as we're forwarding to bindings.

Anyone interested in working on this, please answer in this issue or join our public Graphcool slack and join the conversation!

How to use fragments if model names differ?

In the following i'm using prisma-binding as an example.

Let's say we have a User model in Prisma and a PrivateProfile model in Yoga which maps and relays to the user model.

I have a resolver me, which uses this prisma-binding call

return ctx.db.query.user({ where: { oidcId) } }, info) // passing info!

I'm sending this from playground:

{
  me {
    ...Foo
  }
}
fragment Foo on PrivateProfile {
  id
  image {
    id
    storeId
  }
}

I'm receiving this response:

"Field 'user' of type 'User' must have a sub selection. (line 2, column 3):\n  user(where: $_where)\n  

This is the request sent to Prisma:

query:
query ($_where: UserWhereUniqueInput!) {
  user(where: $_where)
}
operationName: null
variables:
{
  "_where": {
    "oidcId": "auth0|5aa381cef9df373eaf631ad3"
  }
}

It is clear why this happens. Fragments are named. If we take a look into the info object:

info.fragments.Foo.typeCondition.name.value

we get

'PrivateProfile'

which does not exist in Prisma, only in our Yoga schema. The fragment should be defined on User instead.

So if we do:

info.fragments.Foo.typeCondition.name.value = 'User'

At least the error disappears and i get from Prisma:

{
  "user": {
    "name": "Eda Glover",
    "image": {
      "id": "cjeqx8i7c2mbv0151irmx1xo2",
      "storeId": "fooo",
      "__typename": "Image"
    },
    "__typename": "User",
    "isRegisteredAtAnalytics": false,
    "id": "cjeqx8i7c2mbu01515gf2xwng"
  }
}

Still Yoga, does not return the correct response to the client, but at least Prisma does.

How can i use fragments in the bound API and my frontend interface, if my model names are not the same?

TypeError: this.schema.getQueryType is not a function

I get this error when I try to instantiate a new binding class. This is the whole stack trace:

TypeError: this.schema.getQueryType is not a function
    at Binding.buildQueryMethods (/home/capaj/git_projects/tests/type-gql/node_modules/graphql-binding/dist/Binding.js:77:23)
    at Binding.buildMethods (/home/capaj/git_projects/tests/type-gql/node_modules/graphql-binding/dist/Binding.js:65:19)
    at Binding [as constructor] (/home/capaj/git_projects/tests/type-gql/node_modules/graphql-binding/dist/Binding.js:50:20)
    at new Binding (/home/capaj/git_projects/tests/type-gql/node_modules/graphql-binding/dist/makeBindingClass.js:27:27)
    at Object.<anonymous> (/home/capaj/git_projects/tests/type-gql/sample-query.ts:4:13)
    at Module._compile (internal/modules/cjs/loader.js:678:30)
    at Module.m._compile (/tmp/ts-node-dev-hook-499649283482992.js:44:25)
    at Module._extensions..js (internal/modules/cjs/loader.js:689:10)
    at require.extensions.(anonymous function) (/tmp/ts-node-dev-hook-499649283482992.js:46:14)
    at Object.nodeDevHook [as .ts] (/home/capaj/git_projects/tests/type-gql/node_modules/ts-node-dev/lib/hook.js:61:7)

I've set up a new repo so that you can reproduce it easily here: https://github.com/capaj/type-gql-with-binding-boilerplate

just clone, npm i and then run npx ts-node-dev sample-query.ts and you'll get the error.

addFragmentToInfo: TypeError: (0 , _graphqlBinding.default) is not a function

I try to use fragments in Prisma:

export const articleReturn = `{
  ...ArticleFlat 
  translations(where: {lang: $lang}, first: 1) { ...ArticleTranslationFlat }
  type ${require('../type/typesLocalized.resolvers').typeReturn}
  tags ${require('../tag/tagsLocalized.resolvers').tagReturn}
}`;


export async function articlesLocalized(_, args, ctx, info) {
  // prisma binding is injected in context `prisma` property and is injected correctly
  const result = await ctx.prisma.query.articles(
      Object.assign(args, {lang: ctx.lang}),
      articleReturn,
      addFragmentToInfo(info, Object.values(fragments).join('\n'))
  );
  return result.map(entry => collapseArticle(entry));
}

articleReturn value:

{
  ...ArticleFlat 
  translations(where: {lang: $lang}, first: 1) { ...ArticleTranslationFlat }
  type {...TypeFlat label(where: {lang: $lang}, first: 1) {...TypeLabelFlat}}
  tags {...TagFlat label(where: {lang: $lang}, first: 1) {...TagLabelFlat}}
}

Object.values(fragments).join('\n') value:

fragment ArticleFlat on Article {
  id
  createdAt
  updatedAt
  publishedAt
  type
  status
}
fragment ArticleTranslationFlat on ArticleTranslation {
  lang
  title
  body
}
fragment TagFlat on Tag {
  id
  createdAt
  updatedAt
  color
  name
}
fragment TagLabelFlat on TagLabel {
  lang
  label
}
fragment TypeFlat on Type {
  id
  createdAt
  updatedAt
  color
  name
}
fragment TypeLabelFlat on TypeLabel {
  lang
  label
}

And I get error:

TypeError: (0 , _graphqlBinding.default) is not a function
    at _callee$ (..../src/entities/article/articlesLocalized.resolvers.js:39:7)

binding-source does not support ES6 modules yet the generated binding requires ES6 module

so I have a sandbox repo and currently if I generate a binding like this: https://github.com/capaj/type-gql-with-binding-boilerplate/blob/3ad576184fbdf405cf4445f1340728d3064a0947/package.json#L9

the generation is a success. The only problem is, that when I try to use my binding it fails like this: #103
I was thinking that I'll just write my binding source: https://github.com/capaj/type-gql-with-binding-boilerplate/blob/3ad576184fbdf405cf4445f1340728d3064a0947/binding-source.js#L16
as ES6 module. Discovered that it's not supported so I reverted back to fixing it in the generated binding.

so wouldn't it be better if binding source was evaluated as ES6 module?
If that is not an option, could the import be changed from import * as schema from './binding-source' to import { default as schema } from './binding-source'

Note that I have these options enabled in my tsconfig:

    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,

Improve error message when calling mutation that doesn't exist

@sorenbs commented on Sun Dec 17 2017

When calling a misspelled mutation like this:

ctx.db.mutation.updadteWorkspaceInvitation( [...] )

The following (unhelpful) error is returned:

TypeError: Cannot read property 'args' of undefined
    at processRootField (/Users/sorenbs/code/gc/cloud/node_modules/graphql-tools/src/stitching/delegateToSchema.ts:202:37)
    at /Users/sorenbs/code/gc/cloud/node_modules/graphql-tools/src/stitching/delegateToSchema.ts:127:15
    at Array.map (<anonymous>)
    at createDocument (/Users/sorenbs/code/gc/cloud/node_modules/graphql-tools/src/stitching/delegateToSchema.ts:125:28)
    at Object.<anonymous> (/Users/sorenbs/code/gc/cloud/node_modules/graphql-tools/src/stitching/delegateToSchema.ts:51:38)
    at step (/Users/sorenbs/code/gc/cloud/node_modules/graphql-tools/dist/stitching/delegateToSchema.js:40:23)
    at Object.next (/Users/sorenbs/code/gc/cloud/node_modules/graphql-tools/dist/stitching/delegateToSchema.js:21:53)
    at /Users/sorenbs/code/gc/cloud/node_modules/graphql-tools/dist/stitching/delegateToSchema.js:15:71
    at Promise (<anonymous>)
    at __awaiter (/Users/sorenbs/code/gc/cloud/node_modules/graphql-tools/dist/stitching/delegateToSchema.js:11:12)
    at Object.delegateToSchema (/Users/sorenbs/code/gc/cloud/node_modules/graphql-tools/dist/stitching/delegateToSchema.js:49:12)
    at Proxy.<anonymous> (/Users/sorenbs/code/gc/cloud/node_modules/graphcool-binding/src/index.ts:169:14)
    at /Users/sorenbs/code/gc/cloud/src/resolvers/Mutation/workspace.ts:96:27
    at step (/Users/sorenbs/code/gc/cloud/src/resolvers/Mutation/workspace.ts:32:23)
    at Object.next (/Users/sorenbs/code/gc/cloud/src/resolvers/Mutation/workspace.ts:13:53)
    at fulfilled (/Users/sorenbs/code/gc/cloud/src/resolvers/Mutation/workspace.ts:4:58)

@kbrandwijk commented on Sun Dec 17 2017

For reference, I have included that here, let's include that in graphql-binding: https://github.com/supergraphql/supergraph-orm/blob/master/src/utils.ts#L53-L55

Expose all queries and mutations as resolvers

It seems like a common sentiment amongst Prisma users is that they just want to expose the entire generated CRUD API to the client, with a few custom pieces. See the following for evidence of that:

To achieve this, no one wants to copy over parts of the generated schema nor resolvers in any way if possible. That is cumbersome and messy. As a partial solution to this problem, I propose exposing mutation and query resolver objects that have all of the generated mutation and query bindings wrapped as resolvers. This might be done on the Binding class, exposing a queryResolvers property and a mutationResolvers property. Essentially, I think we need to incorporate functionality like the following:

export function prepareTopLevelResolvers(resolverObject: Query | Mutation) {
    return Object.entries(resolverObject).reduce((result, entry) => {
            const resolverName = entry[0];
            const resolverFunction = entry[1];
            return {
                ...result,
                [resolverName]: async (parent, args, context, info) => {
                    return await resolverFunction(args, info);
                }
            };
    }, {});
}

This function does the automatic wrapping and allows you to very easily expose the entire generated API in your application server. See this write-up for a full example application and explanation: https://medium.com/@lastmjs/advanced-graphql-directive-permissions-with-prisma-fdee6f846044

Support delegation to sub fields

Currently schema delegation is only possible for root fields but not for subfields which becomes necessary when for example a most fields are wrapped in a viewer root object.

Here is a syntactical suggestion for how the API could look like:

const resolvers = {
  Query: {
    me: (parent, args, ctx, info) {
      return myBinding.query.viewer({ someArg: true }).user({ otherArg: false }, info)
    }
  }
}

Thoughts about implementation:

  • Requires deeper refactoring to support both Promise interface (.then()) and method chaining (especially tricky to support correct typing). See chromeless for similar example.
  • The last invoked method in the method chain needs to know about the previous calls (implement possibly via some kind of cache) which is then "wrapping" the resolver info object with the tree path described by the previous method chain calls (this also needs to work without an info object passed in)
  • Requires partial rewrite of the static binding generator

Note: The same applies to subscription delegation.

Omitting the info object could put 'promises' into nested fields

Let's say Post has multiple categories inside categories field.

If you query for - db.query.allPosts() - you'll only get scalar fields.

What do you think about having all nested fields as thenable object

eg:

async function getPostCategories(postId) {
  const post = db.query.post(postId);
  return await post.categories
}

under the hood, categories.then() would be fired, and it could be used to start fetching nested field and in fact return desired data without needing to write info object

Drawback is that you'd have to send 2 requests instead of one.

Note that if given field is not desired, then() of it is not fired, so it's request is not sent.

Question: Is there a ways to pass args to the schema?

I'm using the graphql-binding generator to create my typescript file.

projects:
  myapp:
    schemaPath: src/client.graphql
    extensions:
      codegen:
        - generator: graphql-binding
          language: typescript
          input: src/schema.ts
          output:
            binding: src/client.ts
// usage
const client = new Client({endpoint: 'url..'});
// schema.ts
export default makeRemoteExecutableSchema({ link, schema: typeDefs });

How can I exec this endpoint arg if the schema has to be a default export?

Thoughts on resolver forwarding

Based on recent feedback, I'd like to kickstart an open discussion about the state of resolver forwarding and potential improvements.

forwardTo is a great convenience function:

const { forwardTo } = require('prisma-binding')

const Query = {
  posts: forwardTo('db'),
}

Here, we forward the posts query from our GraphQL server to a Prisma API.
However, as soon as we want to modify the behaviour by, say, checking authentication before resolving, we have to fall back to the full ctx.db.query call:

const Query = {
   posts(parent, { where, skip, last, first }, ctx, info) {
    await checkAuth()
    return ctx.db.query.posts({ where, skip, last, first }, info)
  },
}

The best way to tackle this at the moment is to write a little convenience wrapper to check authentication like so:

const Query = {
   posts: checkAuth(forwardTo('db'))
}

Can you come up with other scenarios where resolver forwarding falls short, and approaches to improve the entire experience surrounding resolver forwarding?

#37 is a great idea in that direction, but I'm sure there are more great approaches to be found ๐Ÿ™‚

fragmentReplacements across multiple bindings

Right now, using extractFragmentReplacements only works when exactly one binding is being used.
However, as soon as you want to include multiple separate bindings into your graphql-yoga server, there may be types in one binding that the other binding doesn't know of.
So let's assume we stay with the current API, using multiple bindings would look like this:

// example-resolver.ts with fragment replacement
export const user = {
  fragment: `fragment X on User { id email }`,
  resolver: ({id, email}, args, ctx, info) { return ctx.db.query.user({where: {id}}, info) }
}
// resolvers.ts
import { extractFragmentReplacements } from 'prisma-binding'

export const resolvers = {
 Query: {
    ...
  }
}

export const fragmentReplacements = extractFragmentReplacements(resolvers)
// index.ts

import { GraphQLServer } from 'graphql-yoga'
import { Prisma } from './generated/prisma'
import { resolvers, fragmentReplacements } from './resolvers'
import {CustomBinding1} from './generated/binding1'
import {CustomBinding2} from './generated/binding2'

const server = new GraphQLServer({
  typeDefs: './src/schema.graphql',
  resolvers,
  context: req => ({
    ...req,
    db: new Prisma({
      fragmentReplacements,
      endpoint: process.env.PRISMA_ENDPOINT,
      secret: process.env.PRISMA_SECRET,
      debug: true,
    }),
    binding1: new CustomBinding1({
      fragmentReplacements
    }),
    binding2: new CustomBinding2({
      fragmentReplacements
    }),
  }),
})

To solve this, we could use one of the following 2 strategies:

1. tag syntax

// example-resolver.ts with fragment replacement

export const user = {
  fragment: {
    tag: 'db',
    fragment: `fragment X on User { id email }`,
  },
  resolver: ({id, email}, args, ctx, info) { return ctx.db.query.user({where: {id}}, info) }
}

The db tag would then be used by the Prisma binding as it's used as the context key. It would need to specify a tag like this:

    db: new Prisma({
      fragmentReplacements,
      endpoint: process.env.PRISMA_ENDPOINT,
      secret: process.env.PRISMA_SECRET,
      debug: true,
    }),

2. Ignore unknown types

Each binding could also just ignore unknown fragments on its own.
Let's say, there is a fragment replacement for User, but a specific binding doesn't know of a User type. It would simply ignore it. This get's tricky however, when we're dealing with multiple bindings that know of types with equal name but unequal type definition.

cannot convert a symbol value to string

I have tried to execute the example given in readme, but getting the following error
cannot convert a symbol value to string

const { makeExecutableSchema } = require('graphql-tools')
const { Binding } = require('graphql-binding')

const users = [
  {
    name: 'Alice',
  },
  {
    name: 'Bob',
  },
]

const typeDefs = `
  type Query {
    findUser(name: String!): User
  }
  type User {
    name: String!
  }
`

const resolvers = {
  Query: {
    findUser: (parent, { name }) => users.find(u => u.name === name),
  },
}

const schema = makeExecutableSchema({ typeDefs, resolvers })

const findUserBinding = new Binding({
  schema
})
 console.log(findUserBinding)
 findUserBinding.findUser({ name: 'Bob' })
  .then(result => console.log(result))
 
  
  export default schema

Scalar lists are not queried if the info object is omitted

Omitting the info object from a query is supposed to fetch all scalar fields. This does not seem to be the case for scalar list fields.

Consider this Prisma data model:

type Book {
  id: ID! @unique
  title: String!
  page: [Int!]!
}

If I run this resolver:

const resolvers = {
  Query: {
    async allBooks(parent, args, ctx, info) {
      return await ctx.db.query.books({}) // omitting `info`
    }
  },
}

The result contains id and title, but not page.

Moved from here: prisma-labs/prisma-binding#114

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.