Giter Club home page Giter Club logo

graphql-relay-js's Introduction

GraphQL

GraphQL Logo

The GraphQL specification is edited in the markdown files found in /spec the latest release of which is published at https://graphql.github.io/graphql-spec/.

The latest draft specification can be found at https://graphql.github.io/graphql-spec/draft/ which tracks the latest commit to the main branch in this repository.

Previous releases of the GraphQL specification can be found at permalinks that match their release tag. For example, https://graphql.github.io/graphql-spec/October2016/. If you are linking directly to the GraphQL specification, it's best to link to a tagged permalink for the particular referenced version.

Overview

This is a Working Draft of the Specification for GraphQL, a query language for APIs created by Facebook.

The target audience for this specification is not the client developer, but those who have, or are actively interested in, building their own GraphQL implementations and tools.

In order to be broadly adopted, GraphQL will have to target a wide variety of backend environments, frameworks, and languages, which will necessitate a collaborative effort across projects and organizations. This specification serves as a point of coordination for this effort.

Looking for help? Find resources from the community.

Getting Started

GraphQL consists of a type system, query language and execution semantics, static validation, and type introspection, each outlined below. To guide you through each of these components, we've written an example designed to illustrate the various pieces of GraphQL.

This example is not comprehensive, but it is designed to quickly introduce the core concepts of GraphQL, to provide some context before diving into the more detailed specification or the GraphQL.js reference implementation.

The premise of the example is that we want to use GraphQL to query for information about characters and locations in the original Star Wars trilogy.

Type System

At the heart of any GraphQL implementation is a description of what types of objects it can return, described in a GraphQL type system and returned in the GraphQL Schema.

For our Star Wars example, the starWarsSchema.ts file in GraphQL.js defines this type system.

The most basic type in the system will be Human, representing characters like Luke, Leia, and Han. All humans in our type system will have a name, so we define the Human type to have a field called "name". This returns a String, and we know that it is not null (since all Humans have a name), so we will define the "name" field to be a non-nullable String. Using a shorthand notation that we will use throughout the spec and documentation, we would describe the human type as:

type Human {
  name: String
}

This shorthand is convenient for describing the basic shape of a type system; the JavaScript implementation is more full-featured, and allows types and fields to be documented. It also sets up the mapping between the type system and the underlying data; for a test case in GraphQL.js, the underlying data is a set of JavaScript objects, but in most cases the backing data will be accessed through some service, and this type system layer will be responsible for mapping from types and fields to that service.

A common pattern in many APIs, and indeed in GraphQL is to give objects an ID that can be used to refetch the object. So let's add that to our Human type. We'll also add a string for their home planet.

type Human {
  id: String
  name: String
  homePlanet: String
}

Since we're talking about the Star Wars trilogy, it would be useful to describe the episodes in which each character appears. To do so, we'll first define an enum, which lists the three episodes in the trilogy:

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

Now we want to add a field to Human describing what episodes they were in. This will return a list of Episodes:

type Human {
  id: String
  name: String
  appearsIn: [Episode]
  homePlanet: String
}

Now, let's introduce another type, Droid:

type Droid {
  id: String
  name: String
  appearsIn: [Episode]
  primaryFunction: String
}

Now we have two types! Let's add a way of going between them: humans and droids both have friends. But humans can be friends with both humans and droids. How do we refer to either a human or a droid?

If we look, we note that there's common functionality between humans and droids; they both have IDs, names, and episodes in which they appear. So we'll add an interface, Character, and make both Human and Droid implement it. Once we have that, we can add the friends field, that returns a list of Characters.

Our type system so far is:

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

interface Character {
  id: String
  name: String
  friends: [Character]
  appearsIn: [Episode]
}

type Human implements Character {
  id: String
  name: String
  friends: [Character]
  appearsIn: [Episode]
  homePlanet: String
}

type Droid implements Character {
  id: String
  name: String
  friends: [Character]
  appearsIn: [Episode]
  primaryFunction: String
}

One question we might ask, though, is whether any of those fields can return null. By default, null is a permitted value for any type in GraphQL, since fetching data to fulfill a GraphQL query often requires talking to different services that may or may not be available. However, if the type system can guarantee that a type is never null, then we can mark it as Non Null in the type system. We indicate that in our shorthand by adding an "!" after the type. We can update our type system to note that the id is never null.

Note that while in our current implementation, we can guarantee that more fields are non-null (since our current implementation has hard-coded data), we didn't mark them as non-null. One can imagine we would eventually replace our hardcoded data with a backend service, which might not be perfectly reliable; by leaving these fields as nullable, we allow ourselves the flexibility to eventually return null to indicate a backend error, while also telling the client that the error occurred.

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

interface Character {
  id: String!
  name: String
  friends: [Character]
  appearsIn: [Episode]
}

type Human implements Character {
  id: String!
  name: String
  friends: [Character]
  appearsIn: [Episode]
  homePlanet: String
}

type Droid implements Character {
  id: String!
  name: String
  friends: [Character]
  appearsIn: [Episode]
  primaryFunction: String
}

We're missing one last piece: an entry point into the type system.

When we define a schema, we define an object type that is the basis for all query operations. The name of this type is Query by convention, and it describes our public, top-level API. Our Query type for this example will look like this:

type Query {
  hero(episode: Episode): Character
  human(id: String!): Human
  droid(id: String!): Droid
}

In this example, there are three top-level operations that can be done on our schema:

  • hero returns the Character who is the hero of the Star Wars trilogy; it takes an optional argument that allows us to fetch the hero of a specific episode instead.
  • human accepts a non-null string as a query argument, a human's ID, and returns the human with that ID.
  • droid does the same for droids.

These fields demonstrate another feature of the type system, the ability for a field to specify arguments that configure their behavior.

When we package the whole type system together, defining the Query type above as our entry point for queries, this creates a GraphQL Schema.

This example just scratched the surface of the type system. The specification goes into more detail about this topic in the "Type System" section, and the type directory in GraphQL.js contains code implementing a specification-compliant GraphQL type system.

Query Syntax

GraphQL queries declaratively describe what data the issuer wishes to fetch from whoever is fulfilling the GraphQL query.

For our Star Wars example, the starWarsQueryTests.js file in the GraphQL.js repository contains a number of queries and responses. That file is a test file that uses the schema discussed above and a set of sample data, located in starWarsData.js. This test file can be run to exercise the reference implementation.

An example query on the above schema would be:

query HeroNameQuery {
  hero {
    name
  }
}

The initial line, query HeroNameQuery, defines a query with the operation name HeroNameQuery that starts with the schema's root query type; in this case, Query. As defined above, Query has a hero field that returns a Character, so we'll query for that. Character then has a name field that returns a String, so we query for that, completing our query. The result of this query would then be:

{
  "hero": {
    "name": "R2-D2"
  }
}

Specifying the query keyword and an operation name is only required when a GraphQL document defines multiple operations. We therefore could have written the previous query with the query shorthand:

{
  hero {
    name
  }
}

Assuming that the backing data for the GraphQL server identified R2-D2 as the hero. The response continues to vary based on the request; if we asked for R2-D2's ID and friends with this query:

query HeroNameAndFriendsQuery {
  hero {
    id
    name
    friends {
      id
      name
    }
  }
}

then we'll get back a response like this:

{
  "hero": {
    "id": "2001",
    "name": "R2-D2",
    "friends": [
      {
        "id": "1000",
        "name": "Luke Skywalker"
      },
      {
        "id": "1002",
        "name": "Han Solo"
      },
      {
        "id": "1003",
        "name": "Leia Organa"
      }
    ]
  }
}

One of the key aspects of GraphQL is its ability to nest queries. In the above query, we asked for R2-D2's friends, but we can ask for more information about each of those objects. So let's construct a query that asks for R2-D2's friends, gets their name and episode appearances, then asks for each of their friends.

query NestedQuery {
  hero {
    name
    friends {
      name
      appearsIn
      friends {
        name
      }
    }
  }
}

which will give us the nested response

{
  "hero": {
    "name": "R2-D2",
    "friends": [
      {
        "name": "Luke Skywalker",
        "appearsIn": ["NEWHOPE", "EMPIRE", "JEDI"],
        "friends": [
          { "name": "Han Solo" },
          { "name": "Leia Organa" },
          { "name": "C-3PO" },
          { "name": "R2-D2" }
        ]
      },
      {
        "name": "Han Solo",
        "appearsIn": ["NEWHOPE", "EMPIRE", "JEDI"],
        "friends": [
          { "name": "Luke Skywalker" },
          { "name": "Leia Organa" },
          { "name": "R2-D2" }
        ]
      },
      {
        "name": "Leia Organa",
        "appearsIn": ["NEWHOPE", "EMPIRE", "JEDI"],
        "friends": [
          { "name": "Luke Skywalker" },
          { "name": "Han Solo" },
          { "name": "C-3PO" },
          { "name": "R2-D2" }
        ]
      }
    ]
  }
}

The Query type above defined a way to fetch a human given their ID. We can use it by hard-coding the ID in the query:

query FetchLukeQuery {
  human(id: "1000") {
    name
  }
}

to get

{
  "human": {
    "name": "Luke Skywalker"
  }
}

Alternately, we could have defined the query to have a query parameter:

query FetchSomeIDQuery($someId: String!) {
  human(id: $someId) {
    name
  }
}

This query is now parameterized by $someId; to run it, we must provide that ID. If we ran it with $someId set to "1000", we would get Luke; set to "1002", we would get Han. If we passed an invalid ID here, we would get null back for the human, indicating that no such object exists.

Notice that the key in the response is the name of the field, by default. It is sometimes useful to change this key, for clarity or to avoid key collisions when fetching the same field with different arguments.

We can do that with field aliases, as demonstrated in this query:

query FetchLukeAliased {
  luke: human(id: "1000") {
    name
  }
}

We aliased the result of the human field to the key luke. Now the response is:

{
  "luke": {
    "name": "Luke Skywalker"
  }
}

Notice the key is "luke" and not "human", as it was in our previous example where we did not use the alias.

This is particularly useful if we want to use the same field twice with different arguments, as in the following query:

query FetchLukeAndLeiaAliased {
  luke: human(id: "1000") {
    name
  }
  leia: human(id: "1003") {
    name
  }
}

We aliased the result of the first human field to the key luke, and the second to leia. So the result will be:

{
  "luke": {
    "name": "Luke Skywalker"
  },
  "leia": {
    "name": "Leia Organa"
  }
}

Now imagine we wanted to ask for Luke and Leia's home planets. We could do so with this query:

query DuplicateFields {
  luke: human(id: "1000") {
    name
    homePlanet
  }
  leia: human(id: "1003") {
    name
    homePlanet
  }
}

but we can already see that this could get unwieldy, since we have to add new fields to both parts of the query. Instead, we can extract out the common fields into a fragment, and include the fragment in the query, like this:

query UseFragment {
  luke: human(id: "1000") {
    ...HumanFragment
  }
  leia: human(id: "1003") {
    ...HumanFragment
  }
}

fragment HumanFragment on Human {
  name
  homePlanet
}

Both of those queries give this result:

{
  "luke": {
    "name": "Luke Skywalker",
    "homePlanet": "Tatooine"
  },
  "leia": {
    "name": "Leia Organa",
    "homePlanet": "Alderaan"
  }
}

The UseFragment and DuplicateFields queries will both get the same result, but UseFragment is less verbose; if we wanted to add more fields, we could add it to the common fragment rather than copying it into multiple places.

We defined the type system above, so we know the type of each object in the output; the query can ask for that type using the special field __typename, defined on every object.

query CheckTypeOfR2 {
  hero {
    __typename
    name
  }
}

Since R2-D2 is a droid, this will return

{
  "hero": {
    "__typename": "Droid",
    "name": "R2-D2"
  }
}

This was particularly useful because hero was defined to return a Character, which is an interface; we might want to know what concrete type was actually returned. If we instead asked for the hero of Episode V:

query CheckTypeOfLuke {
  hero(episode: EMPIRE) {
    __typename
    name
  }
}

We would find that it was Luke, who is a Human:

{
  "hero": {
    "__typename": "Human",
    "name": "Luke Skywalker"
  }
}

As with the type system, this example just scratched the surface of the query language. The specification goes into more detail about this topic in the "Language" section, and the language directory in GraphQL.js contains code implementing a specification-compliant GraphQL query language parser and lexer.

Validation

By using the type system, it can be predetermined whether a GraphQL query is valid or not. This allows servers and clients to effectively inform developers when an invalid query has been created, without having to rely on runtime checks.

For our Star Wars example, the file starWarsValidationTests.js contains a number of demonstrations of invalid operations, and is a test file that can be run to exercise the reference implementation's validator.

To start, let's take a complex valid query. This is the NestedQuery example from the above section, but with the duplicated fields factored out into a fragment:

query NestedQueryWithFragment {
  hero {
    ...NameAndAppearances
    friends {
      ...NameAndAppearances
      friends {
        ...NameAndAppearances
      }
    }
  }
}

fragment NameAndAppearances on Character {
  name
  appearsIn
}

And this query is valid. Let's take a look at some invalid queries!

When we query for fields, we have to query for a field that exists on the given type. So as hero returns a Character, we have to query for a field on Character. That type does not have a favoriteSpaceship field, so this query:

# INVALID: favoriteSpaceship does not exist on Character
query HeroSpaceshipQuery {
  hero {
    favoriteSpaceship
  }
}

is invalid.

Whenever we query for a field and it returns something other than a scalar or an enum, we need to specify what data we want to get back from the field. Hero returns a Character, and we've been requesting fields like name and appearsIn on it; if we omit that, the query will not be valid:

# INVALID: hero is not a scalar, so fields are needed
query HeroNoFieldsQuery {
  hero
}

Similarly, if a field is a scalar, it doesn't make sense to query for additional fields on it, and doing so will make the query invalid:

# INVALID: name is a scalar, so fields are not permitted
query HeroFieldsOnScalarQuery {
  hero {
    name {
      firstCharacterOfName
    }
  }
}

Earlier, it was noted that a query can only query for fields on the type in question; when we query for hero which returns a Character, we can only query for fields that exist on Character. What happens if we want to query for R2-D2s primary function, though?

# INVALID: primaryFunction does not exist on Character
query DroidFieldOnCharacter {
  hero {
    name
    primaryFunction
  }
}

That query is invalid, because primaryFunction is not a field on Character. We want some way of indicating that we wish to fetch primaryFunction if the Character is a Droid, and to ignore that field otherwise. We can use the fragments we introduced earlier to do this. By setting up a fragment defined on Droid and including it, we ensure that we only query for primaryFunction where it is defined.

query DroidFieldInFragment {
  hero {
    name
    ...DroidFields
  }
}

fragment DroidFields on Droid {
  primaryFunction
}

This query is valid, but it's a bit verbose; named fragments were valuable above when we used them multiple times, but we're only using this one once. Instead of using a named fragment, we can use an inline fragment; this still allows us to indicate the type we are querying on, but without naming a separate fragment:

query DroidFieldInInlineFragment {
  hero {
    name
    ... on Droid {
      primaryFunction
    }
  }
}

This has just scratched the surface of the validation system; there are a number of validation rules in place to ensure that a GraphQL query is semantically meaningful. The specification goes into more detail about this topic in the "Validation" section, and the validation directory in GraphQL.js contains code implementing a specification-compliant GraphQL validator.

Introspection

It's often useful to ask a GraphQL schema for information about what queries it supports. GraphQL allows us to do so using the introspection system!

For our Star Wars example, the file starWarsIntrospectionTests.js contains a number of queries demonstrating the introspection system, and is a test file that can be run to exercise the reference implementation's introspection system.

We designed the type system, so we know what types are available, but if we didn't, we can ask GraphQL, by querying the __schema field, always available on the root type of a Query. Let's do so now, and ask what types are available.

query IntrospectionTypeQuery {
  __schema {
    types {
      name
    }
  }
}

and we get back:

{
  "__schema": {
    "types": [
      {
        "name": "Query"
      },
      {
        "name": "Character"
      },
      {
        "name": "Human"
      },
      {
        "name": "String"
      },
      {
        "name": "Episode"
      },
      {
        "name": "Droid"
      },
      {
        "name": "__Schema"
      },
      {
        "name": "__Type"
      },
      {
        "name": "__TypeKind"
      },
      {
        "name": "Boolean"
      },
      {
        "name": "__Field"
      },
      {
        "name": "__InputValue"
      },
      {
        "name": "__EnumValue"
      },
      {
        "name": "__Directive"
      }
    ]
  }
}

Wow, that's a lot of types! What are they? Let's group them:

  • Query, Character, Human, Episode, Droid - These are the ones that we defined in our type system.
  • String, Boolean - These are built-in scalars that the type system provided.
  • __Schema, __Type, __TypeKind, __Field, __InputValue, __EnumValue, __Directive - These all are preceded with a double underscore, indicating that they are part of the introspection system.

Now, let's try and figure out a good place to start exploring what queries are available. When we designed our type system, we specified what type all queries would start at; let's ask the introspection system about that!

query IntrospectionQueryTypeQuery {
  __schema {
    queryType {
      name
    }
  }
}

and we get back:

{
  "__schema": {
    "queryType": {
      "name": "Query"
    }
  }
}

And that matches what we said in the type system section, that the Query type is where we will start! Note that the naming here was just by convention; we could have named our Query type anything else, and it still would have been returned here if we had specified it as the starting type for queries. Naming it Query, though, is a useful convention.

It is often useful to examine one specific type. Let's take a look at the Droid type:

query IntrospectionDroidTypeQuery {
  __type(name: "Droid") {
    name
  }
}

and we get back:

{
  "__type": {
    "name": "Droid"
  }
}

What if we want to know more about Droid, though? For example, is it an interface or an object?

query IntrospectionDroidKindQuery {
  __type(name: "Droid") {
    name
    kind
  }
}

and we get back:

{
  "__type": {
    "name": "Droid",
    "kind": "OBJECT"
  }
}

kind returns a __TypeKind enum, one of whose values is OBJECT. If we asked about Character instead:

query IntrospectionCharacterKindQuery {
  __type(name: "Character") {
    name
    kind
  }
}

and we get back:

{
  "__type": {
    "name": "Character",
    "kind": "INTERFACE"
  }
}

We'd find that it is an interface.

It's useful for an object to know what fields are available, so let's ask the introspection system about Droid:

query IntrospectionDroidFieldsQuery {
  __type(name: "Droid") {
    name
    fields {
      name
      type {
        name
        kind
      }
    }
  }
}

and we get back:

{
  "__type": {
    "name": "Droid",
    "fields": [
      {
        "name": "id",
        "type": {
          "name": null,
          "kind": "NON_NULL"
        }
      },
      {
        "name": "name",
        "type": {
          "name": "String",
          "kind": "SCALAR"
        }
      },
      {
        "name": "friends",
        "type": {
          "name": null,
          "kind": "LIST"
        }
      },
      {
        "name": "appearsIn",
        "type": {
          "name": null,
          "kind": "LIST"
        }
      },
      {
        "name": "primaryFunction",
        "type": {
          "name": "String",
          "kind": "SCALAR"
        }
      }
    ]
  }
}

Those are our fields that we defined on Droid!

id looks a bit weird there, it has no name for the type. That's because it's a "wrapper" type of kind NON_NULL. If we queried for ofType on that field's type, we would find the String type there, telling us that this is a non-null String.

Similarly, both friends and appearsIn have no name, since they are the LIST wrapper type. We can query for ofType on those types, which will tell us what these are lists of.

query IntrospectionDroidWrappedFieldsQuery {
  __type(name: "Droid") {
    name
    fields {
      name
      type {
        name
        kind
        ofType {
          name
          kind
        }
      }
    }
  }
}

and we get back:

{
  "__type": {
    "name": "Droid",
    "fields": [
      {
        "name": "id",
        "type": {
          "name": null,
          "kind": "NON_NULL",
          "ofType": {
            "name": "String",
            "kind": "SCALAR"
          }
        }
      },
      {
        "name": "name",
        "type": {
          "name": "String",
          "kind": "SCALAR",
          "ofType": null
        }
      },
      {
        "name": "friends",
        "type": {
          "name": null,
          "kind": "LIST",
          "ofType": {
            "name": "Character",
            "kind": "INTERFACE"
          }
        }
      },
      {
        "name": "appearsIn",
        "type": {
          "name": null,
          "kind": "LIST",
          "ofType": {
            "name": "Episode",
            "kind": "ENUM"
          }
        }
      },
      {
        "name": "primaryFunction",
        "type": {
          "name": "String",
          "kind": "SCALAR",
          "ofType": null
        }
      }
    ]
  }
}

Let's end with a feature of the introspection system particularly useful for tooling; let's ask the system for documentation!

query IntrospectionDroidDescriptionQuery {
  __type(name: "Droid") {
    name
    description
  }
}

yields

{
  "__type": {
    "name": "Droid",
    "description": "A mechanical creature in the Star Wars universe."
  }
}

So we can access the documentation about the type system using introspection, and create documentation browsers, or rich IDE experiences.

This has just scratched the surface of the introspection system; we can query for enum values, what interfaces a type implements, and more. We can even introspect on the introspection system itself. The specification goes into more detail about this topic in the "Introspection" section, and the introspection file in GraphQL.js contains code implementing a specification-compliant GraphQL query introspection system.

Additional Content

This README walked through the GraphQL.js reference implementation's type system, query execution, validation, and introspection systems. There's more in both GraphQL.js and specification, including a description and implementation for executing queries, how to format a response, explaining how a type system maps to an underlying implementation, and how to format a GraphQL response, as well as the grammar for GraphQL.

Contributing to this repo

This repository is managed by EasyCLA. Project participants must sign the free (GraphQL Specification Membership agreement before making a contribution. You only need to do this one time, and it can be signed by individual contributors or their employers.

To initiate the signature process please open a PR against this repo. The EasyCLA bot will block the merge if we still need a membership agreement from you.

You can find detailed information here. If you have issues, please email [email protected].

If your company benefits from GraphQL and you would like to provide essential financial support for the systems and people that power our community, please also consider membership in the GraphQL Foundation.

graphql-relay-js's People

Contributors

benjie avatar christensenemc avatar danielrearden avatar davide-ganito avatar dependabot[bot] avatar dschafer avatar exogen avatar flipside avatar guigrpa avatar iquabius avatar ivangoncharov avatar jamiehodge avatar josephsavona avatar kassens avatar koistya avatar leebyron avatar lencioni avatar macrostart avatar michaelchum avatar mike-marcacci avatar mormahr avatar mortezaalizadeh avatar motiz88 avatar saihaj avatar seanchas avatar skevy avatar steveluscher avatar taion avatar vslinko avatar wincent 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  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

graphql-relay-js's Issues

Why is there no 'position' argument?

I guess this is as much a question to the graphql+relay spec, but I'm confused with regards to the lack of 'position' as an argument. I know that the reason it uses complicated pagination is because of unbounded and order-independent results, but I have a use case I'm not sure how to go about doing in graphql+relay (though it's quite easy so I'm sure I just missed something).

How do I, say, get the 5th item (and only 5th item), if my list is ordered (by, say, an orderBy argument).

Handling ordering and filtering in connections

The need come from displaying the user a list of nodes that she should be able to sort/filter.

I'd like to get a better grasp on the philosophy behind Connections and how would one go in building a backend to support the {before,after,first,last} pagination along with ordering/filtering.

From graphql/graphql-spec#4 I understand that GraphQL per se doesn't really care on how you handle pagination/filtering/ordering. Hence I'm writing here even if I realize that this is not strictly in the Relay scope.

My general question is: how do you go in handling filtering and especially sorting the Relay way (if at all) and why?

Lets say we could query with a Relay server like:

currentUser {
  friends(first:10, after:"opaque-cursor", order:AGE) {
    edges: [ ... ]
  }
}

I guess my questions are:

  • would that connection make sense (in that example AGE would be an Enum value) or would be querying for a different firendsOrderedByAge connection be more aligned with Relay philosophy? Or what else?
  • say you want to combine multiple ordering, how would you go about that? I'm currently trying with ie: friends(order:[AGE,POSTCOUNT], ...)
  • as the API I'm currently trying to proxy via a Relay server does not map well with the Connection model, how would you go in building a backend API that does?
  • I'm trying to cut all the way to the SQL to at least make this work. say you get back {after_age, after_id} from your opaque cursor in the after parameter; would a pseudo-SQL like this make sense? SELECT * FROM a_table WHERE (age, id) > (after_age, after_id) ORDER BY age ASC, id ASC LIMIT first

Thank you all!

Difficult to use connectionFields

When resolving a connectionType, it'd be nice if you could pass optional information back to custom connectionFields defined within customDefinitions.

I'm working on a search connection type atm and I want to be able to set as connection fields total and took (the search time). Those would necessarily only be known within the searchConnection resolve function.

If given some guidance on a solution, I can put together a PR tomorrow.

cursorForObjectInConnection uses indexOf method

Array.indexOf is not very flexible and if I am retrieving item and array separately from a db, I have to manually find an element in an array to pass it into cursorForObjectInConnection.
Wouldn't it be more appropriate to use findIndex or isEqual from Lo-Dash?

PageInfo Incorrect for Dynamically Retrieved Arrays

My resolve function retrieves data from a database using the standard first argument as a limit.

resolve(_, args) =>  {
   criteria = makeCriteriaFromArguments(args);
   criteria.limit = args.first;
   const result = stuffRepository.findAll(criteria);
   return connectionFromPromisedArray(result, args);
};

In this case, when connectionFromPromisedArray thunks over to connectionFromArraySlice, the end offset and the upperbound will be equal (https://github.com/graphql/graphql-relay-js/blob/master/src/connection/arrayconnection.js#L121). The resultant PageInfo, emitted by connectionFromPromisedArray would have

{ hasNextPage: false, ... } 

For this use case, I would expect this:

hasNextPage: first != null ? endOffset <= upperBound : false,

instead of

hasNextPage: first != null ? endOffset < upperBound : false,

I could resolve this by overfetching my collection by one, but that would be really hacky.

Split up connectionArgs

Would it be possible to define connectionArgs per https://github.com/graphql/graphql-relay-js/blob/v0.3.2/src/connection/connection.js#L29 as e.g. the union of:

export var connectionWithFirstArgs: GraphQLFieldConfigArgumentMap = {
  after: {
    type: GraphQLString
  },
  first: {
    type: GraphQLInt
  },
};

export var connectionWithLastArgs: GraphQLFieldConfigArgumentMap = {
  before: {
    type: GraphQLString
  },
  last: {
    type: GraphQLInt
  },
};

export var connectionArgs: GraphQLFieldConfigArgumentMap = {
  ...connectionWithFirstArgs,
  ...connectionWithLastArgs,
};

Per the spec: http://facebook.github.io/relay/graphql/connections.htm#sec-Arguments, connections don't have to implement both sets of arguments, so it'd be nice to have a helper directly from graphql-relay-js for defining just one pagination direction.

Is there some good way to type check with `graphql` ?

I used a flow-type output version graphql-relay-js in my project. And at this time the graphql/graphql-relay-js's master branch have some unpassed flow-type check.
The most critical part for type check is something like resolve.( Cause raw graphql allow source to mixed type.and args to a simple map, Where Relay narrowed source to object ,and use a single input key for args. And ,the most of unpassed type checks in graphql-relay with graphql are caused by this).

GraphQLFieldResolveFn (graphql#L469)

type GraphQLFieldResolveFn = (
  source: mixed,
  args: {[argName: string]: mixed},
  context: mixed,
  info: GraphQLResolveInfo
) => mixed

which in Relay should be something like :

ype RelayFieldResolveFn = (
  source: Object,
  args: {'input':Object},
  context: mixed,
  info: GraphQLResolveInfo
) => mixed

Is there some elegant way to type check graphql-relay with graphql ( and narrowed resolveFn rightly)?
i got two idea,but
seems modified GraphQLFieldResolveFn to some template type will lead to lots of changes in graphql.
And adding some rumtime check in graphql-relay looks like inefficiently.
Is there some good way to do this?

Abstract type resolution for node definitions

The current implementation of nodeDefinitions seems to be a pretty thin wrapper around the object resolution and abstract type resolution. This makes for an interface to nodeDefinitions that feels fairly awkward.

For example, here: https://github.com/relayjs/relay-starter-kit/blob/9f30837849362b65181e6383cfd52ad402a9c00e/data/schema.js#L52 - it feels like it'd be much easier to do something like return {type: userType, value: getUser(id)} instead of separately specifying the type resolver in a different function below. I realize I can get something like a more decoupled syntax if I use isTypeOf, but it seems strange to have to have any sort of explicit type resolution logic if I know a priori the concrete type at the time that I resolve the node value.

Is there any chance of modifying the implementation or the spec in such a way that it would be possible to simultaneously resolve both the result and the concrete type for abstract types, or at least add some wrappers for doing so for Relay nodes?

Better support for async connections

Hey, a pretty common situation is that getting the list of connected IDs is cheap (i.e. the list of the IDs of your friends), but resolving each ID is expensive.

Specifically for this kind of use case: clayallsopp/graphqlhub@07fec4c#diff-6cd9d3d473d2f9b30b961ebfb3d3f048R50 - I have the entire list of IDs in memory, so if the query specifies first: 5 or provides a cursor, it would be great to reduce the set of IDs we're resolving before we resolve them. the current connectionFromPromisedArray doesnt help in this regard, as you have to resolve the entire list and then it trims after-the-fact

I think something like this API would be very helpful in general - I think it's possible? (assuming you have the entire list of IDs in memory and cursors are pure functions of offset and IDs)

let connectionIds = item.kids;
return promisedConnectionFromIdsArray(connectionIds, args, (trimmedIds) => {
  return Promise.all(trimmedIds.map(getItem));
});

Connection is based on `array`,is this a design style guide for design a relay server?

In connection/arrayconnection.js, It seems all the function is tend to work with array.
For example offsetToCursor is the only way to generate Cursor. Does this mean its a design pattern i must follow, or imply that i should generate Cursor by myself when using something other than array.If im planning to use Mongodb,should i make the database interface like an static array ?

BTW:
As a newbie to web develop, im a bit confused how to implement a qualified relay server.
Are there some guide for design a graphql-relay server, should i follow all the way in graphql-relay-js, which Database Facebook used with relay-server ? mysql or ?
Im not sure ask this here is appropriate or not,but the topic for graphql-relay-js is rarely on the web.
Thanks a lot, forgive my impolite.

var PREFIX = 'arrayconnection:';
/**
 * Creates the cursor string from an offset.
 */
export function offsetToCursor(offset: number): ConnectionCursor {
  return base64(PREFIX + offset);
}

Evaluate the interfaces field lazily (if defined as a thunk)

Sharing nodeInterface across the different GraphQL types in a splitted schema can lead to weird circular dependency issues.

Considering the fact that the different field types (in mutationWithClientMutationId or GraphQLObjectType) are evaluated lazily when defined as thunks, could we make it so the interfaces field is also evaluated lazily when defined as a thunk ?

Currently, it's evaluated immediately no matter how it is defined, which is not the expected behavior.

Add CHANGELOG

I just noticed while preparing the v0.3.3 release that we don't have an actual changelog, so I had to link people to: v0.3.2...v0.3.3

Let's add one, so we can have something more human-readable, albeit at a slight maintenance cost.

Translating cursors to original object id's

Hey guys,

I was wondering if anyone could shed some light on how edge cursors work, and how they are generated.

I am trying to leverage the paging functionality provided by "connections", but I am not sure whether my cursors are being generated properly. At first I thought a cursor was the global id of the edge object, which I could have converted in its original id using fromGlobalId(args.after) but it seems that isn't the case.

I need to get at the original id, so that i can structure my SQL query to only retrieve results that occur after my original id. My sql database has no knowledge of the cursors generated in graphql, so I'm at a loss on how to make that connection.

Thanks in advance!

Does relay support pagination when the size of the collection is unknown?

Does relay support pagination when the size of the collection is unknown? ie sometimes it is expensive to know the exact size of a collection or just needed from a product perspective. Like search results.

While I understand you need the exact size to support the 'last' argument if 'before' or 'first', what's the most relay-compliant way to support those cases? Maybe the answer is to not worry about it (pick an arbitrary long size) and make sure relay never emits a 'last' query on that connection. Any thoughts?

On a related note, any best practice on how to handle a non-static collection? like a facebook feed or an online chat list.

Thanks!

(I guess my questions are about about the spec, but this repo seems also appropriate.)

Either warn or allow for IDs to contain `:` characters

I was recently building a toy project with Relay and GraphQL where I needed to encode extra information in my global ID. To do this, I decided to add an idFetcher argument to my globalIdField call that returned a string that was delimited by a :. However, this did not work because of how globalIds are deconstructed in fromGlobalId, so instead of getting my encoded ID (e.g. 123:test) I just got the ID part (e.g. 123).

To prevent this confusion, it might be nice to either warn when this happens or use a different strategy in fromGlobalId than split. Here's a performant version that satisfies this use-case:

export function fromGlobalId(globalId: string): ResolvedGlobalId {
  var unbasedGlobalId = unbase64(globalId);
  var delimiterPos = unbasedGlobalId.indexOf(':');
  return {
    type: unbasedGlobalId.substring(0, delimiterPos),
    id: unbasedGlobalId.substring(delimiterPos + 1)
  };
}

Of course, you could also use a regex if you prefer (but I think I like the substring solution):

export function fromGlobalId(globalId: string): ResolvedGlobalId {
  var tokens = unbase64(globalId).match(/(.+?):(.+)/);
  return {
    type: tokens[1],
    id: tokens[2]
  };
}

Let me know what you think. I'll probably just submit a PR for this change for your convenience.

nodeDefination for multiple role

We could check rootValue or context to determine request's role permission for graphql request.
How do we check permission for nodeDefination (request thru 'node' ) ?

Make `typeResolver` optional in `nodeDefinitions`

AFAICT graphql-js allows you to leave out resolveType here:

https://github.com/graphql/graphql-js/blob/a149b89a3047f38750eaade1f220eb1317eb9217/src/type/definition.js#L684-L685

In which case it will fall back to using getTypeOf/isTypeOf

The problem is, nodeDefinitions doesn't seem to allow you to leave out the typeResolver function:

resolveType: typeResolver

Making this optional would certainly reduce a lot of boilerplate code

Update `typeResolver` to receive `context`

Currently typeResolver does not accept context.
It would be useful to expose it.

I wanted to submit a PR and tried the following patch but graphql always passes undefined.

diff --git a/src/node/node.js b/src/node/node.js
index 30320ab..2c8d7bd 100644
--- a/src/node/node.js
+++ b/src/node/node.js
@@ -16,8 +16,8 @@ import {

 import type {
   GraphQLFieldConfig,
-  GraphQLObjectType,
-  GraphQLResolveInfo
+  GraphQLResolveInfo,
+  GraphQLTypeResolveFn
 } from 'graphql';

 import {
@@ -30,9 +30,6 @@ type GraphQLNodeDefinitions = {
   nodeField: GraphQLFieldConfig
 }

-type typeResolverFn = (object: any) => ?GraphQLObjectType |
-                      (object: any) => ?Promise<GraphQLObjectType>;
-
 /**
  * Given a function to map from an ID to an underlying object, and a function
  * to map from an underlying object to the concrete GraphQLObjectType it
@@ -45,7 +42,7 @@ type typeResolverFn = (object: any) => ?GraphQLObjectType |
  */
 export function nodeDefinitions(
   idFetcher: ((id: string, context: any, info: GraphQLResolveInfo) => any),
-  typeResolver?: ?typeResolverFn
+  typeResolver?: ?GraphQLTypeResolveFn
 ): GraphQLNodeDefinitions {
   var nodeInterface = new GraphQLInterfaceType({
     name: 'Node',

can hasPreviousPage and hasNextPage both be true?

Looking at this test case:

describe('pagination', () => {
it('respects first and after', () => {
var c = connectionFromArray(
letters,
{first: 2, after: 'YXJyYXljb25uZWN0aW9uOjE='}
);
return expect(c).to.deep.equal({
edges: [
{
node: 'C',
cursor: 'YXJyYXljb25uZWN0aW9uOjI=',
},
{
node: 'D',
cursor: 'YXJyYXljb25uZWN0aW9uOjM=',
},
],
pageInfo: {
startCursor: 'YXJyYXljb25uZWN0aW9uOjI=',
endCursor: 'YXJyYXljb25uZWN0aW9uOjM=',
hasPreviousPage: false,
hasNextPage: true,
}
});
});

  describe('pagination', () => {
    it('respects first and after', () => {
      var c = connectionFromArray(
        letters,
        {first: 2, after: 'YXJyYXljb25uZWN0aW9uOjE='}
      );
      return expect(c).to.deep.equal({
        edges: [
          {
            node: 'C',
            cursor: 'YXJyYXljb25uZWN0aW9uOjI=',
          },
          {
            node: 'D',
            cursor: 'YXJyYXljb25uZWN0aW9uOjM=',
          },
        ],
        pageInfo: {
          startCursor: 'YXJyYXljb25uZWN0aW9uOjI=',
          endCursor: 'YXJyYXljb25uZWN0aW9uOjM=',
          hasPreviousPage: false,
          hasNextPage: true,
        }
      });
    });

Because there is also an A and a B - shouldn't hasPreviousPage be true? If not, then how do we know there is a previous page available??

Q: nodeInterface without typeResolver function

I can get nodeInterface and nodeField from nodeDefinitions like this

let {nodeInterface, nodeField} = nodeDefinitions(
  (globalId) => {
     return null;
  }
);

And the first questions is, that If I need just nodeInterface and I will not query on nodeFiled, can I return always null? Or I will need it somewhere?

If I did not pass typeResolve as second parameter for nodeDefentions, in the execution moment for each runtime GraphQLObjectType will be called isTypeOf().

So why should I check the type in isTypeOfmethod?

Next example working fine.

let {nodeInterface, nodeField} = nodeDefinitions(
  (globalId) => {
    // If I need just nodeInterface and I will not query on nodeFiled, can I return always null? Or I will need this for nodeConnections?
    return null;
  }
);

let userType = new GraphQLObjectType({
  name: 'User',
  description: 'User in application',
  isTypeOf: (value, info) => {
    // Can I always return true for each GraphQLObjectType which uses nodeInterface without typeResolver function?
    return true
  },

  fields: () => ({
    id: globalIdField('User'),
    name: {
      type: GraphQLString
    }
  }),
  interfaces: [nodeInterface]
});

let oneMoreType = new GraphQLObjectType({
  name: 'OneMoreType',
  description: 'Some another type in application',
  isTypeOf: (value, info) => {
    // Can I always return true for each GraphQLObjectType which uses nodeInterface without typeResolver function?
    return true;
  },
  fields: () => ({
    id: globalIdField('User'),
    field: {
      type: GraphQLString
    }
  }),
  interfaces: [nodeInterface]
});


const schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'Root',
    description: 'Root field in application',
    fields: () => ({
      user: {
        type: userType,
        resolve() {
          return {
            id: '1',
            name: 'First'
          }
        }
      },
      oneMoreType: {
        type: oneMoreType,
        resolve() {
          return {
            id: '3',
            field: 'Some field'
          }
        }
      }
    })
  })
});

export default schema;

Release?

Hi guys!

I noticed that there's been a ton of commits since Nov 2 (the last release)...a lot of them are super useful!

Could you release a new version to npm? Currently I'm having to install this from Sinopia...but I'd like to go back to npm.

Thanks!
-Adam

Unable to resolve type in nodeDefinitions

I'm fetching data from MySQL database, but I'm unable to resolve types of returned data by instanceof operator or by it's content (they are just pure JS objects, they don't have any specific class). My nodeDefinitions looks something like that:

let {nodeInterface, nodeField} = nodeDefinitions(
  (globalId) => {
    let {type, id} = fromGlobalId(globalId)
    return db.findOne(type, id)
  },
  (obj) => {
    // can't resolve type of obj
  }
)

What is the prefered way to solve this problem? So far I invented just this, but it's ugly because it change returned object:

let {nodeInterface, nodeField} = nodeDefinitions(
  (globalId) => {
    let {type, id} = fromGlobalId(globalId)
    return Object.assign({_type: type}, db.findOne(type, id))
  },
  (obj) => {
    if(obj._type === 'User') {
      return userType
    } else if(obj._type === 'Article') {
      return articleType
    }
    // ... and so on
  }
)

Can you think of any better solution? Thank you.

Common to be needing fromGlobalId in mutateAndGetPayload?

I'm slowly learning my way around Relay, and have written a few mutations so far with great success. None of my prior mutations have modified an existing record, though, so I don't have an example yet where I specify the id of something I'd like to update.

The mutation I'm currently working on will do just that, finally. I'm wanting to update an existing record. My mutation expects the id of the record as an argument. Coming from Relay, this is a globally unique id created with toGlobalId, though. So, within mutateAndGetPayload I've got access to that global id, which I figure I need to use fromGlobalId to get my local SQL primary key value.

Is this the typical use case? I need to code in a type check for this id to make sure it's of the right type, etc? Or is there a way to say that the id parameter needs to be the same type as the id on my other type, and it'll do that fromGlobalId for me?

Here's my mutation ...

import {mutationWithClientMutationId} from "graphql-relay";
import {GraphQLID, GraphQLNonNull} from "graphql";
import AccountType from "../type/AccountType";
import TeamInviteType from "../type/TeamInviteType";
import * as AccountSql from "../../data/query/sql/account";
import * as TeamInviteSql from "../../data/query/sql/team-invite";

export default mutationWithClientMutationId({
  name: "AcceptTeamInvite",
  inputFields: {
    id: {
      type: new GraphQLNonNull(GraphQLID),
      description: "The id of the team invite."
    }
  },
  outputFields: {
    invite: {
      type: new GraphQLNonNull(TeamInviteType),
      resolve: source => source
    },
    account: {
      type: new GraphQLNonNull(AccountType),
      resolve: source => AccountSql.getById(source.account)
    }
  },
  mutateAndGetPayload: async (source, info) => {
    const {user} = info.rootValue;
    if (!user) {
      return Error("not allowed");
    }
    const invite = await TeamInviteSql.getById(source.id);
    if (user.id !== invite.account) {
      return Error("not allowed");
    }
    return TeamInviteSql.accept(invite.id);
  }
});

mutateAndGetPayload get rootValue

How can I get in method mutateAndGetPayload rootValue ?

graphQLServer.use('/', graphQLHTTP((req) => (
  { schema: Schema, rootValue: { req }, pretty: true }
)));

mutateAndGetPayload resolving promises and returning error instead of payload?

I've only had about 10 minutes to look into this before I have to jet, but I wanted to create an issue so that I could check back when I return and see if anybody has some insight here.

I've got a few database query functions that return promises. On failure, those functions reject the promise.

In my mutationWithClientMutationId code, I want to use these promise functions and basically never return an actual failure HTTP response, but instead I'd like to respond with an error message as part of the response payload. I'll deal with true server side failures later, but I'm trying to get some useful user error messages back to the React side of things.

I've got some code though that uses the promises in an attempt to respond with a payload that includes the error messages, but it seems to instead respond with the actual HTTP error response, instead of a 200 response but with my output fields. Here's my code ...

import {mutationWithClientMutationId} from "graphql-relay";
import {GraphQLString, GraphQLBoolean, GraphQLNonNull} from "graphql";
import {Sql} from "../../data";

function fetchToken({username, password}) {
  return Sql.Account.getWebToken(username, password)
    .then(token => ({ token }))
    .catch(error => ({ error }));
};

function createAccount({username, password, summoner}) {
  return Sql.Account.insert(username, password, summoner)
    .then(fetchToken)
    .catch(error => ({ error }));
};

export default mutationWithClientMutationId({
  name: "CreateAccount",
  inputFields: {
    username: {
      type: new GraphQLNonNull(GraphQLString)
    },
    password: {
      type: new GraphQLNonNull(GraphQLString)
    },
    summoner: {
      type: new GraphQLNonNull(GraphQLString)
    }
  },
  outputFields: {
    token: {
      type: GraphQLString,
      resolve: source => source.token
    },
    userError: {
      type: GraphQLString,
      resolve: source => source.error
    },
    didSucceed: {
      type: new GraphQLNonNull(GraphQLBoolean),
      resolve: source => source.error === undefined
    }
  },
  mutateAndGetPayload: createAccount
});

Am I using these promises incorrectly? The Sql functions return promises. The resolve functions appear to be receiving the proper output from the createAccount or fetchToken functions (an error object). GraphQL is responding with an HTTP error though, as opposed to the desired successful response.

Spec violation in connectionFromArraySlice

Hi,

It looks like there is a spec violation in connectionFromArraySlice implementation. Specifically, spec of the Relay pagination algorithm (4.3 / ApplyCursorsToEdges / Step 2.2) indicates that elements should be removed from the slice only if afterEdge exists (if afterEdge does not exist, allEdges are returned) [1]. Here is a counter example:

connectionFromArraySlice(
  [1, 2, 3, 4, 5, 6],
  { first: 6, after: 'YXJyYXljb25uZWN0aW9uOjEw' },
  { sliceStart: 0, arrayLength: 6 } )

This returns empty edges array. Argument after in this case is base64 encoded "arrayconnection:10" string. Clearly, edge corresponding to that cursor doesn't exist, so connectionFromArraySlice should return all six nodes in the edges array as per the spec.

There is no symmetrical issue with last / before combination, because endOffset is set to min of beforeOffset and array length.

Just wanted to confirm that my read of the spec makes sense to you all before sending a pull request with the fix.

Thanks,
Nemanja

[1] Spec: https://facebook.github.io/relay/graphql/connections.htm#ApplyCursorsToEdges()

[Object Identification] Customisable ID field.

Our web applications have been using GraphQL for a while now and those teams built out the GraphQL service that wraps our backends, however, they are not yet using React or Relay. We’re now using it in our iOS app, but through React Native and Relay. For the first version of out iOS app, that’s currently in the app store, we did not yet needed specific Relay support in our GraphQL service.

Now I’m trying to add full Relay support to the GraphQL service, but am struggling a bit with the required schema to support Global Object Identification. The issue is that our schema already has an id field, that maps to an ID on the backend. The various web clients are already using this assumption all over the place, but what’s worse is that the iOS app also assumes this in 1 place, which means that if I ever change the semantics of the id field it basically breaks for all people that have not yet upgraded the app.

In our case, the place where the iOS app uses it isn’t 100% critical (it’s related to some analytics code) and the web clients are all in our control, so those could all be patched and upgraded. However, it seems to me that id is such a common and ambiguous name, it might make more sense to be able to change the field that Relay depends on instead.

My thought is to update Relay to either:

  • make the field customisable and default to id
  • or, seeing as I don’t like options myself, maybe just start with preferring e.g. relayID over id, if that field exists in the schema

Would either be acceptable?

What is the use case for returning the cursor of each individual node?

Hi,

I was wondering what is the use case for returning the cursor along with every node in a connection. Given that pageInfo has the start and end cursor, that would suffice for pagination purposes as far as I understand.

The reason I'm asking is because there is too much indirection in the schema (and therefore, in the queries) when retrieving fields from a connection:

{
  articles {
   pageInfo { ... }
    edges {
      node {
        title
      }
    }
  }
}

Ideally this could be:

{
  articles {
   pageInfo { ... }
    nodes {
      title
    }
  }
}

which removes one level of indirection.

Thanks for your time.

Include rootValue in mutations?

I'm trying to implement a login mutation, which would mutate the user's session. I used the example shown on [express-graphql|https://github.com/graphql/express-graphql#advanced-options] to add the session to the rootValue, but now I can't access that session in mutateAndGetPayload. I recommend adding rootValue to the mutateAndGetPayload call: necessary changes [here|https://github.com/graphql/graphql-relay-js/blob/master/src/mutation/mutation.js#L85].

mutationWithClientMutationId stripes description field

I am trying to define a new mutation with a description:

const createNode = mutationWithClientMutationId({
    name: 'createNode',
    description: 'Creates a new node in the system',

    inputFields,
    outputFields
});

As I can see in the source code, it is not possible to provide a description field to the mutation.
Is this behavior correct?

Run PageInfo.startCursor and endCursor through resolveCursor() ?

In the case that you have a custom cursor defined via resolveCursor, I would think the generated pageInfo should also be run through it, else the pageInfo.startCursor / pageInfo.endCursor will be the default cursors, not the custom ones.

I actually ended up getting around this issue by generating my own pageInfo from scratch, since in my case connectionFromArray wasn't doing anything special for me (I'm pulling data from a DB, and don't have access to allItems), but if this was an oversight but intended behavior, I thought I'd bring it to light.

export const {connectionType: messageConnection, edgeType: MessageEdge} = connectionDefinitions({
  name: 'Message',
  nodeType: MessageType,
  resolveCursor: ({cursor, node}) => {
    return Base64.encode('createdAt:'+moment(node.get('createdAt')).unix());
  },
});
export const PostType = new GraphQLObjectType({
  name: 'Post',
  description: 'Post',
  fields: () => ({
    id: globalIdField('Post'),
    messages: {
      type: messageConnection,
      args: connectionArgs,
      resolve: async (post, args) => {
        let messages = await getMessages(post.id, args)
        let connection = connectionFromArray(messages, args);
        // connection.edges is still using the default cursors, because resolveCursor will only get called after this resolve returns
        // likewise, connection.pageInfo.startCursor and connection.pageInfo.endCursor are using the default cursors, but they won't be re-resolved using my custom resolveCursor()
        return connection;
      },
    },
  }),
  interfaces: [nodeInterface],
});

Naming suggestions: PagedAssocation, Items, Item

You're gonna hate this one 😸

I know, I know, naming things, etc. But I'd be remiss if I didn't at least make some suggestions.

So, I think Connections could be called PagedAssociations – or something along those lines. I feel like Connection has a very network/database connotation to it, and it makes it quite confusing for newcomers approaching a library that does actually deal with both network connections and probably database connections too – especially because they're being defined in the schema alongside database resolving functions.

I also think edges and node would perhaps be a little more newbie friendly if they were just items and item.

`node` type should not be restricted to `ObjectType`

Based on the ConnectionConfig, node type is required to be a GraphQLObjectType:

https://github.com/graphql/graphql-relay-js/blob/master/src/connection/connection.js#L62

But according to the spec:

An “Edge Type” must contain a field called node. This field must return either a Scalar, Enum, Object, Interface, Union, or a Non‐Null wrapper around one of those types. Notably, this field cannot return a list.

https://facebook.github.io/relay/graphql/connections.htm#sec-Node

Is it a bug, or did I misunderstood the spec?

pageInfo.hasPreviousPage is always false if you are paging forwards

hasPreviousPage: last != null ? startOffset > lowerBound : false,

If my connection args don't have the 'last' parameter, then hasPreviousPage will always be false. Same problem if you are paging backwards and don't have the 'first' parameter in your connection args: hasNextPage will always be false.

But I can't have a 'last' parameter if I am paging forward using 'first' and 'after'. And I can't have a 'first' parameter if I am paging backwards using 'last' and 'before'. Babel-relay-plugin will throw an error on transpile.

So if I am paging forwards, I will always be told I have no previous pages, even when I do. And if I am paging backwards I will always be told I have no next pages, even when I do.

This has gotta be a bug. It kinda ruins bi-directional paging.

Can't we just make paging easier and let us pass first and last and before and after all as connection args (some of them as null depending on which way you are paging) without babel-relay-plugin blowing up?

Where do the (`graphql` flowTypes) used in `graphql-relay-js` come from ?

I saw some imported type from graphql.
like connection.js#L20 :

import type {
  GraphQLFieldConfigArgumentMap,
  GraphQLFieldConfigMap
} from 'graphql';

But i can neither find .js.flow output in graphql's package, nor a ".flowconfig"-style lib, and there is also no graphql in flow's lib too.
So where do these types come from? Did the type check pass ,just because if flow can not found a flowed package, it will set those type to any type?
Or is there some new way for outputting flowTypes?
:D

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.