From 506edd50c510bdbc744aa58582dd3f8b95071565 Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Wed, 31 Aug 2016 00:26:33 -0700 Subject: [PATCH 01/12] Sketch of new schema --- site/docs/Learn-Schema.md | 23 +++++--- site/docs/_swapiSchema.js | 112 ++++++++++++++++++++++++++++++++++---- 2 files changed, 116 insertions(+), 19 deletions(-) diff --git a/site/docs/Learn-Schema.md b/site/docs/Learn-Schema.md index 8db514c87a..247e8e6e30 100644 --- a/site/docs/Learn-Schema.md +++ b/site/docs/Learn-Schema.md @@ -41,7 +41,7 @@ The most basic components of a GraphQL schema are object types, which just repre ```graphql type Character { name: String! - appearsIn: [Episode] + appearsIn: [Episode]! } ``` @@ -55,6 +55,11 @@ The language is pretty readable, but let's go over it so that we can have a shar Now you know what a GraphQL object type looks like, and how to read the basics of the GraphQL type language. +### Arguments + +TODO +- Include the idea of default arguments + ### The Query and Mutation types Most types in your schema will just be normal object types, but there are two types that are special within a schema: @@ -85,7 +90,7 @@ That means that the GraphQL service needs to have a `Query` type with `hero` and ```graphql type Query { hero(episode: Episode): Character - droid(id: ID!): Droid + droid(id: String!): Droid } ``` @@ -155,7 +160,7 @@ Object types, scalars, and enums are the only kinds of types you can define in G ```graphql type Character { name: String! - appearsIn: [Episode] + appearsIn: [Episode]! } ``` @@ -213,9 +218,9 @@ For example, you could have an interface `Character` that represents any charact ```graphql interface Character { id: String! - name: String + name: String! friends: [Character] - appearsIn: [Episode] + appearsIn: [Episode]! } ``` @@ -226,17 +231,17 @@ For example, here are some types that might implement `Character`: ```graphql type Human implements Character { id: String! - name: String + name: String! friends: [Character] - appearsIn: [Episode] + appearsIn: [Episode]! homePlanet: String } type Droid implements Character { id: String! - name: String + name: String! friends: [Character] - appearsIn: [Episode] + appearsIn: [Episode]! primaryFunction: String } ``` diff --git a/site/docs/_swapiSchema.js b/site/docs/_swapiSchema.js index c6b7a8988f..2a74dda821 100644 --- a/site/docs/_swapiSchema.js +++ b/site/docs/_swapiSchema.js @@ -16,17 +16,109 @@ import { GraphQLString } from 'graphql'; +const schemaString = ` +schema { + query: Query + mutation: Mutation +} + +type Query { + hero(episode: Episode): Character + search(query: String): [SearchResult] +} + +# The episodes in the Star Wars trilogy +enum Episode { + # Star Wars Episode IV: A New Hope, released in 1977. + NEWHOPE + + # Star Wars Episode V: The Empire Strikes Back, released in 1980. + EMPIRE + + # Star Wars Episode VI: Return of the Jedi, released in 1983. + JEDI +} + +# A character from the Star Wars universe +interface Character { + # The ID of the character + id: ID! + + # The name of the character + name: String! + + # The friends of the character, or an empty list if they have none + friends: [Character] + + # The movies this character appears in + appearsIn: [Episode]! +} + +# A humanoid creature from the Star Wars universe +type Human implements Character { + # The ID of the human + id: ID! + + # What this human calls themselves + name: String! + + # This human's friends, or an empty list if they have none + friends: [Character] + + # The movies this human appears in + appearsIn: [Episode]! + + # A list of starships this person has piloted, or an empty list if none + starships: [Starship] +} + +# An autonomous mechanical character in the Star Wars universe +type Droid implements Character { + # The ID of the droid + id: ID! + + # What others call this droid + name: String! + + # This droid's friends, or an empty list if they have none + friends: [Character] + + # The movies this droid appears in + appearsIn: [Episode]! + + # This droid's primary function + primaryFunction: String +} + +# Units of length +enum LengthUnit { + # The standard unit around the world + METER + + # Primarily used in the United States + FOOT +} + +type Starship { + # The ID of the starship + id: ID! + + # The name of the starship + name: String! + + # The length of the starship, in meters + length(unit: LengthUnit = METER): Float! +} + +union SearchResult = Character | Starship +`; + + + + + + -/** - * This is designed to be an end-to-end test, demonstrating - * the full GraphQL stack. - * - * We will create a GraphQL schema that describes the major - * characters in the original Star Wars trilogy. - * - * NOTE: This may contain spoilers for the original Star - * Wars trilogy. - */ /** * This defines a basic set of data for our Star Wars Schema. From 75ff190e053b9d35017104b14e20bca3fb4f47ac Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Wed, 31 Aug 2016 01:01:49 -0700 Subject: [PATCH 02/12] Add mutations and input type --- site/docs/Learn-Queries.md | 23 +++++++++++------------ site/docs/_swapiSchema.js | 19 ++++++++++++++++++- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/site/docs/Learn-Queries.md b/site/docs/Learn-Queries.md index 88eb6e24e3..a3b4d3a76c 100644 --- a/site/docs/Learn-Queries.md +++ b/site/docs/Learn-Queries.md @@ -186,31 +186,30 @@ Here's an example of a simple mutation: ```graphql mutation CreateCharacterInEpisode($name: String!, $appearsIn: Episode!) { createCharacter(name: $name) - addCharacterToEpisode(name: $name, episode: $appearsIn) } ``` -You can see that a mutation can contain multiple fields, just like a query. There's one important distinction between queries, and mutations, other than the name: - -**While query fields are executed in parallel, mutation fields run in series, one after the other.** - -This means that even though we sent `createCharacter` and `addCharacterToEpisode` in one request, the first is guaranteed to finish before the second begins, ensuring that we create the character before trying to add it an episode. - #### Returning data from mutations -Just like in queries, you can ask for nested fields in the mutation result. This can be useful for fetching the new state of an object after an update: +Just like in queries, if the mutation field returns an object type, you can ask for nested fields. This can be useful for fetching the new state of an object after an update: ```graphql -mutation IncrementCredits($characterId: ID!) { - incrementCredits(characterId: $characterId) { +mutation IncrementCredits($humanId: ID!) { + incrementCredits(humanId: $humanId) { totalCredits } } ``` -In this case, the `incrementCredits` mutation field returns a `Character` object, so we can query the new value of `totalCredits` after giving that character some more credits. Otherwise, we would have needed to send two requests - one to update the credits, and another to get the new value - or guess at the new amount based on outdated data. +In this case, the `incrementCredits` mutation field returns a `Human` object, so we can query the new value of `totalCredits` after giving that character some more credits. Otherwise, we would have needed to send two requests - one to update the credits, and another to get the new value - or guess at the new amount based on outdated data. + +#### Multiple fields in mutations + +A mutation can contain multiple fields, just like a query. There's one important distinction between queries and mutations, other than the name: + +**While query fields are executed in parallel, mutation fields run in series, one after the other.** -That's all! Now you know everything you need to know about GraphQL queries and mutations to build a pretty good application. For more advanced features and tips, check out the advanced section. +This means that if we send two `incrementCredits` mutations in one request, the first is guaranteed to finish before the second begins, ensuring that we don't end up with a race condition with ourselves. ### Fragments and type conditions diff --git a/site/docs/_swapiSchema.js b/site/docs/_swapiSchema.js index 2a74dda821..8be5889c47 100644 --- a/site/docs/_swapiSchema.js +++ b/site/docs/_swapiSchema.js @@ -24,7 +24,12 @@ schema { type Query { hero(episode: Episode): Character - search(query: String): [SearchResult] + search(text: String): [SearchResult] +} + +type Mutation { + createHuman(human: HumanInput!): ID! + incrementCredits(humanId: ID!): Human } # The episodes in the Star Wars trilogy @@ -70,6 +75,9 @@ type Human implements Character { # A list of starships this person has piloted, or an empty list if none starships: [Starship] + + # The number of credits this human has + totalCredits: Int } # An autonomous mechanical character in the Star Wars universe @@ -90,6 +98,15 @@ type Droid implements Character { primaryFunction: String } +# The object to be passed in when creating a new human +input HumanInput { + # The name of the new human + name: String! + + # The movies this human appears in + appearsIn: [Episode]! +} + # Units of length enum LengthUnit { # The standard unit around the world From 3725e5d4625ad32d4659edacfde762dbb61d1943 Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Wed, 31 Aug 2016 01:19:20 -0700 Subject: [PATCH 03/12] Some updates to examples for current WIP --- site/docs/Learn-Schema.md | 23 +++++++---------------- site/docs/_swapiSchema.js | 3 +++ 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/site/docs/Learn-Schema.md b/site/docs/Learn-Schema.md index 247e8e6e30..ce4d52bbd9 100644 --- a/site/docs/Learn-Schema.md +++ b/site/docs/Learn-Schema.md @@ -90,7 +90,7 @@ That means that the GraphQL service needs to have a `Query` type with `hero` and ```graphql type Query { hero(episode: Episode): Character - droid(id: String!): Droid + droid(id: ID!): Droid } ``` @@ -217,7 +217,7 @@ For example, you could have an interface `Character` that represents any charact ```graphql interface Character { - id: String! + id: ID! name: String! friends: [Character] appearsIn: [Episode]! @@ -230,15 +230,16 @@ For example, here are some types that might implement `Character`: ```graphql type Human implements Character { - id: String! + id: ID! name: String! friends: [Character] appearsIn: [Episode]! - homePlanet: String + starships: [Starship] + totalCredits: Int } type Droid implements Character { - id: String! + id: ID! name: String! friends: [Character] appearsIn: [Episode]! @@ -283,17 +284,7 @@ Union types are very similar to interfaces, but they don't get to specify any co XXX no example in SWAPI ```graphql -union SearchResult = Photo | Person - -type Person { - name: String - age: Int -} - -type Photo { - height: Int - width: Int -} +union SearchResult = Character | Starship ``` In this case, if you query a field that returns the `SearchResult` union type, you need to use a conditional fragment to be able to query any fields at all. diff --git a/site/docs/_swapiSchema.js b/site/docs/_swapiSchema.js index 8be5889c47..df4f1fa91f 100644 --- a/site/docs/_swapiSchema.js +++ b/site/docs/_swapiSchema.js @@ -25,6 +25,9 @@ schema { type Query { hero(episode: Episode): Character search(text: String): [SearchResult] + droid(id: ID!): Droid + human(id: ID!): Human + starship(id: ID!): Starship } type Mutation { From 3a80f7870f748e46d81722ec86b28b33d98850be Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Wed, 31 Aug 2016 17:38:39 -0700 Subject: [PATCH 04/12] Add reviews as a very simple mutation, stick to enum for episode --- site/docs/_swapiSchema.js | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/site/docs/_swapiSchema.js b/site/docs/_swapiSchema.js index df4f1fa91f..996baa519a 100644 --- a/site/docs/_swapiSchema.js +++ b/site/docs/_swapiSchema.js @@ -24,6 +24,7 @@ schema { type Query { hero(episode: Episode): Character + reviews(episode: Episode!): [Review] search(text: String): [SearchResult] droid(id: ID!): Droid human(id: ID!): Human @@ -31,8 +32,7 @@ type Query { } type Mutation { - createHuman(human: HumanInput!): ID! - incrementCredits(humanId: ID!): Human + createReview(episode: Episode, review: ReviewInput!): Review } # The episodes in the Star Wars trilogy @@ -101,13 +101,21 @@ type Droid implements Character { primaryFunction: String } -# The object to be passed in when creating a new human -input HumanInput { - # The name of the new human - name: String! +type Review { + # The number of stars this review gave, 1-5 + stars: Int - # The movies this human appears in - appearsIn: [Episode]! + # Comments about the movie + commentary: String +} + +# The input object sent when someone is creating a new review +input ReviewInput { + # 0-5 stars + stars: Int + + # Comments about the movie + commentary: String } # Units of length From 6564d84e71c98c845f38253c5c6df91eac7fd8e5 Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Thu, 1 Sep 2016 14:29:12 -0700 Subject: [PATCH 05/12] Modify schema and data --- site/docs/_swapiSchema.js | 69 +++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/site/docs/_swapiSchema.js b/site/docs/_swapiSchema.js index 996baa519a..1f09b89c51 100644 --- a/site/docs/_swapiSchema.js +++ b/site/docs/_swapiSchema.js @@ -22,6 +22,7 @@ schema { mutation: Mutation } +# The query type, represents all of the entry points into our object graph type Query { hero(episode: Episode): Character reviews(episode: Episode!): [Review] @@ -31,6 +32,7 @@ type Query { starship(id: ID!): Starship } +# The mutation type, represents all updates we can make to our data type Mutation { createReview(episode: Episode, review: ReviewInput!): Review } @@ -62,6 +64,15 @@ interface Character { appearsIn: [Episode]! } +# Units of height +enum LengthUnit { + # The standard unit around the world + METER + + # Primarily used in the United States + FOOT +} + # A humanoid creature from the Star Wars universe type Human implements Character { # The ID of the human @@ -70,6 +81,12 @@ type Human implements Character { # What this human calls themselves name: String! + # Height in the preferred unit, default is meters + height(unit: LengthUnit = METER): Float + + # Mass in kilograms, or null if unknown + mass: Float + # This human's friends, or an empty list if they have none friends: [Character] @@ -78,9 +95,6 @@ type Human implements Character { # A list of starships this person has piloted, or an empty list if none starships: [Starship] - - # The number of credits this human has - totalCredits: Int } # An autonomous mechanical character in the Star Wars universe @@ -101,32 +115,24 @@ type Droid implements Character { primaryFunction: String } +# Represents a review for a movie type Review { # The number of stars this review gave, 1-5 - stars: Int + stars: Int! - # Comments about the movie + # Comment about the movie commentary: String } # The input object sent when someone is creating a new review input ReviewInput { # 0-5 stars - stars: Int + stars: Int! - # Comments about the movie + # Comment about the movie, optional commentary: String } -# Units of length -enum LengthUnit { - # The standard unit around the world - METER - - # Primarily used in the United States - FOOT -} - type Starship { # The ID of the starship id: ID! @@ -134,20 +140,13 @@ type Starship { # The name of the starship name: String! - # The length of the starship, in meters - length(unit: LengthUnit = METER): Float! + # Length of the starship, along the longest axis + length(unit: LengthUnit = METER): Float } -union SearchResult = Character | Starship +union SearchResult = Human | Droid | Starship `; - - - - - - - /** * This defines a basic set of data for our Star Wars Schema. * @@ -161,7 +160,9 @@ var luke = { name: 'Luke Skywalker', friends: [ '1002', '1003', '2000', '2001' ], appearsIn: [ 4, 5, 6 ], - homePlanet: 'Tatooine', + height: 1.72, + mass: 77, + starships: [ '12', '22' ], // XXX }; var vader = { @@ -169,7 +170,9 @@ var vader = { name: 'Darth Vader', friends: [ '1004' ], appearsIn: [ 4, 5, 6 ], - homePlanet: 'Tatooine', + height: 2.02, + mass: 136, + starships: [ '13' ], // XXX }; var han = { @@ -177,6 +180,9 @@ var han = { name: 'Han Solo', friends: [ '1000', '1003', '2001' ], appearsIn: [ 4, 5, 6 ], + height: 1.8, + mass: 80, + starships: [ '10', '22' ], // XXX }; var leia = { @@ -184,7 +190,9 @@ var leia = { name: 'Leia Organa', friends: [ '1000', '1002', '2000', '2001' ], appearsIn: [ 4, 5, 6 ], - homePlanet: 'Alderaan', + height: 1.5, + mass: 49, + starships: [], }; var tarkin = { @@ -192,6 +200,9 @@ var tarkin = { name: 'Wilhuff Tarkin', friends: [ '1001' ], appearsIn: [ 4 ], + height: 1.8, + mass: null, + starships: [], }; var humanData = { From 038adb5d82c1bd8ee841c79d344e331ef4681afd Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Thu, 1 Sep 2016 15:00:07 -0700 Subject: [PATCH 06/12] Start replacing with new schema --- site/docs/Learn-Queries.md | 4 +-- site/docs/_swapiSchema.js | 70 ++++++++++++++++++++++++++++++++------ 2 files changed, 62 insertions(+), 12 deletions(-) diff --git a/site/docs/Learn-Queries.md b/site/docs/Learn-Queries.md index a3b4d3a76c..f62bb1666e 100644 --- a/site/docs/Learn-Queries.md +++ b/site/docs/Learn-Queries.md @@ -225,8 +225,8 @@ query HeroForEpisode($ep: Episode!) { ... on Droid { primaryFunction } - ... on Droid { - homePlanet + ... on Human { + height } } } diff --git a/site/docs/_swapiSchema.js b/site/docs/_swapiSchema.js index 1f09b89c51..dd70733e2f 100644 --- a/site/docs/_swapiSchema.js +++ b/site/docs/_swapiSchema.js @@ -16,6 +16,8 @@ import { GraphQLString } from 'graphql'; +import { makeExecutableSchema } from 'graphql-tools'; + const schemaString = ` schema { query: Query @@ -159,7 +161,7 @@ var luke = { id: '1000', name: 'Luke Skywalker', friends: [ '1002', '1003', '2000', '2001' ], - appearsIn: [ 4, 5, 6 ], + appearsIn: [ 'NEWHOPE', 'EMPIRE', 'JEDI' ], height: 1.72, mass: 77, starships: [ '12', '22' ], // XXX @@ -169,7 +171,7 @@ var vader = { id: '1001', name: 'Darth Vader', friends: [ '1004' ], - appearsIn: [ 4, 5, 6 ], + appearsIn: [ 'NEWHOPE', 'EMPIRE', 'JEDI' ], height: 2.02, mass: 136, starships: [ '13' ], // XXX @@ -179,7 +181,7 @@ var han = { id: '1002', name: 'Han Solo', friends: [ '1000', '1003', '2001' ], - appearsIn: [ 4, 5, 6 ], + appearsIn: [ 'NEWHOPE', 'EMPIRE', 'JEDI' ], height: 1.8, mass: 80, starships: [ '10', '22' ], // XXX @@ -189,7 +191,7 @@ var leia = { id: '1003', name: 'Leia Organa', friends: [ '1000', '1002', '2000', '2001' ], - appearsIn: [ 4, 5, 6 ], + appearsIn: [ 'NEWHOPE', 'EMPIRE', 'JEDI' ], height: 1.5, mass: 49, starships: [], @@ -199,7 +201,7 @@ var tarkin = { id: '1004', name: 'Wilhuff Tarkin', friends: [ '1001' ], - appearsIn: [ 4 ], + appearsIn: [ 'NEWHOPE' ], height: 1.8, mass: null, starships: [], @@ -217,7 +219,7 @@ var threepio = { id: '2000', name: 'C-3PO', friends: [ '1000', '1002', '1003', '2001' ], - appearsIn: [ 4, 5, 6 ], + appearsIn: [ 'NEWHOPE', 'EMPIRE', 'JEDI' ], primaryFunction: 'Protocol', }; @@ -225,7 +227,7 @@ var artoo = { id: '2001', name: 'R2-D2', friends: [ '1000', '1002', '1003' ], - appearsIn: [ 4, 5, 6 ], + appearsIn: [ 'NEWHOPE', 'EMPIRE', 'JEDI' ], primaryFunction: 'Astromech', }; @@ -253,7 +255,7 @@ function getFriends(character) { * Allows us to fetch the undisputed hero of the Star Wars trilogy, R2-D2. */ function getHero(episode) { - if (episode === 5) { + if (episode === 'EMPIRE') { // Luke is the hero of Episode V. return luke; } @@ -515,6 +517,54 @@ var queryType = new GraphQLObjectType({ * Finally, we construct our schema (whose starting query type is the query * type we defined above) and export it. */ -export var StarWarsSchema = new GraphQLSchema({ - query: queryType +// export var StarWarsSchema = new GraphQLSchema({ +// query: queryType +// }); + +const resolvers = { + Query: { + hero: (root, { episode }) => getHero(episode), + human: (root, { id }) => getHuman(id), + droid: (root, { id }) => getDroid(id), + starship: () => null, + reviews: () => null, + search: () => null, + }, + Mutation: { + createReview: () => null, + }, + Character: { + __resolveType(data, context, info){ + if(humanData[data.id]){ + return info.schema.getType('Human'); + } + if(droidData[data.id]){ + return info.schema.getType('Droid'); + } + return null; + }, + }, + Human: { + height: ({ height }) => height, // XXX add units + friends: ({ friends }) => friends.map(getCharacter), + starships: () => null, + appearsIn: ({ appearsIn }) => appearsIn, + }, + Droid: { + friends: ({ friends }) => friends.map(getCharacter), + appearsIn: ({ appearsIn }) => appearsIn, + }, + Starship: { + length: () => null, + } +} + +/** + * Finally, we construct our schema (whose starting query type is the query + * type we defined above) and export it. + */ +console.log('hi') +export const StarWarsSchema = makeExecutableSchema({ + typeDefs: [schemaString], + resolvers }); From c460982d23f8555bf71d94a8d39e3d1679e1b337 Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Thu, 1 Sep 2016 15:06:39 -0700 Subject: [PATCH 07/12] Fake mutation --- site/docs/Learn-Queries.md | 20 ++++++-------------- site/docs/_swapiSchema.js | 2 +- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/site/docs/Learn-Queries.md b/site/docs/Learn-Queries.md index f62bb1666e..fb2f8c4983 100644 --- a/site/docs/Learn-Queries.md +++ b/site/docs/Learn-Queries.md @@ -181,22 +181,14 @@ Most discussions of GraphQL focus on data fetching, but any complete data platfo In REST, any request might end up causing some side-effects on the server, but by convention it's suggested that one doesn't use `GET` requests to modify data. GraphQL is similar - technically any query could be implemented to cause a data write. However, it's useful to establish a convention that any operations that cause writes should be sent explicitly via a mutation. -Here's an example of a simple mutation: +Just like in queries, if the mutation field returns an object type, you can ask for nested fields. This can be useful for fetching the new state of an object after an update. Let's look at a simple example mutation: ```graphql -mutation CreateCharacterInEpisode($name: String!, $appearsIn: Episode!) { - createCharacter(name: $name) -} -``` - -#### Returning data from mutations - -Just like in queries, if the mutation field returns an object type, you can ask for nested fields. This can be useful for fetching the new state of an object after an update: - -```graphql -mutation IncrementCredits($humanId: ID!) { - incrementCredits(humanId: $humanId) { - totalCredits +# { "graphiql": true, "variables": { "ep": "JEDI", "review": { "stars": 5, "commentary": "This is a great movie!" } } } +mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) { + createReview(episode: $ep, review: $review) { + stars + commentary } } ``` diff --git a/site/docs/_swapiSchema.js b/site/docs/_swapiSchema.js index dd70733e2f..9c3835f1c5 100644 --- a/site/docs/_swapiSchema.js +++ b/site/docs/_swapiSchema.js @@ -531,7 +531,7 @@ const resolvers = { search: () => null, }, Mutation: { - createReview: () => null, + createReview: (root, { episode, review }) => review, }, Character: { __resolveType(data, context, info){ From d729c5b32fe40c42ce37ab71aab811513ee4b5af Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Thu, 1 Sep 2016 15:21:45 -0700 Subject: [PATCH 08/12] Update inline fragment docs, other fields --- site/docs/Learn-Schema.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/site/docs/Learn-Schema.md b/site/docs/Learn-Schema.md index ce4d52bbd9..833837584f 100644 --- a/site/docs/Learn-Schema.md +++ b/site/docs/Learn-Schema.md @@ -79,7 +79,7 @@ query { hero { name } - droid(id: "2001") { + droid(id: "2000") { name } } @@ -170,7 +170,7 @@ The Non-Null type modifier can also be used when defining arguments for a field, ```graphql # { "graphiql": true, "variables": { "id": null } } -query DroidById($id: String!) { +query DroidById($id: ID!) { droid(id: $id) { name } @@ -249,7 +249,9 @@ type Droid implements Character { You can see that both of these types have all of the fields from the `Character` interface, but also bring in extra fields, `homePlanet` and `primaryFunction`, that are specific to that particular type of character. -Interfaces are useful when you want to return an object or set of objects, but those might be of several different types. For example, in the following query: +Interfaces are useful when you want to return an object or set of objects, but those might be of several different types. + +For example, note that the following query produces an error: ```graphql # { "graphiql": true, "variables": { "ep": "JEDI" } } @@ -261,7 +263,9 @@ query HeroForEpisode($ep: Episode!) { } ``` -The `hero` field returns the type `Character`, which means it might be either a `Human` or a `Droid` depending on the `episode` argument. In the query above, you can only ask for fields that exist on the `Character` interface, and to ask for a field on a specific object type, you need to use an inline fragment: +The `hero` field returns the type `Character`, which means it might be either a `Human` or a `Droid` depending on the `episode` argument. In the query above, you can only ask for fields that exist on the `Character` interface, which doesn't include `primaryFunction`. + +To ask for a field on a specific object type, you need to use an inline fragment: ```graphql # { "graphiql": true, "variables": { "ep": "JEDI" } } From 0299c2e2f2a606c6db8d6460ac704c167eeb626a Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Thu, 1 Sep 2016 15:48:23 -0700 Subject: [PATCH 09/12] Reformat data, add search section --- site/docs/Learn-Queries.md | 2 +- site/docs/Learn-Schema.md | 28 ++- site/docs/_swapiSchema.js | 458 +++++++++++-------------------------- 3 files changed, 158 insertions(+), 330 deletions(-) diff --git a/site/docs/Learn-Queries.md b/site/docs/Learn-Queries.md index fb2f8c4983..399f03d845 100644 --- a/site/docs/Learn-Queries.md +++ b/site/docs/Learn-Queries.md @@ -228,4 +228,4 @@ In this query, the `hero` field returns the type `Character`, which might be eit To ask for a field on the concrete type, you need to use an _inline fragment_ with a type condition. Because the first fragment is labeled as `... on Droid`, the `primaryFunction` field will only be executed if the `Character` returned from `hero` is of the `Droid` type. Similarly for the `homePlanet` field for the `Human` type. -Named fragments can also be used in the same way, since a named fragment always has a type condition attached. +Named fragments can also be used in the same way, since a named fragment always has a type attached. diff --git a/site/docs/Learn-Schema.md b/site/docs/Learn-Schema.md index 833837584f..a71de70c26 100644 --- a/site/docs/Learn-Schema.md +++ b/site/docs/Learn-Schema.md @@ -285,10 +285,30 @@ Learn more about this in the [inline fragments](XXX) section in the query guide. Union types are very similar to interfaces, but they don't get to specify any common fields between the types. -XXX no example in SWAPI - ```graphql -union SearchResult = Character | Starship +union SearchResult = Human | Droid | Starship ``` -In this case, if you query a field that returns the `SearchResult` union type, you need to use a conditional fragment to be able to query any fields at all. +Wherever we return a `SearchResult` type in our schema, we might get a `Human`, a `Droid`, or a `Starship`. Note that members of a union type need to be concrete object types; you can't create a union type out of interfaces or other unions. + +In this case, if you query a field that returns the `SearchResult` union type, you need to use a conditional fragment to be able to query any fields at all: + +```graphql +# { "graphiql": true} +{ + search(text: "an") { + ... on Human { + name + height + } + ... on Droid { + name + primaryFunction + } + ... on Starship { + name + length + } + } +} +``` diff --git a/site/docs/_swapiSchema.js b/site/docs/_swapiSchema.js index 9c3835f1c5..2b3c516132 100644 --- a/site/docs/_swapiSchema.js +++ b/site/docs/_swapiSchema.js @@ -157,84 +157,108 @@ union SearchResult = Human | Droid | Starship * JSON objects in a more complex demo. */ -var luke = { - id: '1000', - name: 'Luke Skywalker', - friends: [ '1002', '1003', '2000', '2001' ], - appearsIn: [ 'NEWHOPE', 'EMPIRE', 'JEDI' ], - height: 1.72, - mass: 77, - starships: [ '12', '22' ], // XXX -}; - -var vader = { - id: '1001', - name: 'Darth Vader', - friends: [ '1004' ], - appearsIn: [ 'NEWHOPE', 'EMPIRE', 'JEDI' ], - height: 2.02, - mass: 136, - starships: [ '13' ], // XXX -}; - -var han = { - id: '1002', - name: 'Han Solo', - friends: [ '1000', '1003', '2001' ], - appearsIn: [ 'NEWHOPE', 'EMPIRE', 'JEDI' ], - height: 1.8, - mass: 80, - starships: [ '10', '22' ], // XXX -}; - -var leia = { - id: '1003', - name: 'Leia Organa', - friends: [ '1000', '1002', '2000', '2001' ], - appearsIn: [ 'NEWHOPE', 'EMPIRE', 'JEDI' ], - height: 1.5, - mass: 49, - starships: [], -}; - -var tarkin = { - id: '1004', - name: 'Wilhuff Tarkin', - friends: [ '1001' ], - appearsIn: [ 'NEWHOPE' ], - height: 1.8, - mass: null, - starships: [], -}; - -var humanData = { - 1000: luke, - 1001: vader, - 1002: han, - 1003: leia, - 1004: tarkin, -}; - -var threepio = { - id: '2000', - name: 'C-3PO', - friends: [ '1000', '1002', '1003', '2001' ], - appearsIn: [ 'NEWHOPE', 'EMPIRE', 'JEDI' ], - primaryFunction: 'Protocol', -}; - -var artoo = { - id: '2001', - name: 'R2-D2', - friends: [ '1000', '1002', '1003' ], - appearsIn: [ 'NEWHOPE', 'EMPIRE', 'JEDI' ], - primaryFunction: 'Astromech', -}; - -var droidData = { - 2000: threepio, - 2001: artoo, -}; +const humans = [ + { + id: '1000', + name: 'Luke Skywalker', + friends: [ '1002', '1003', '2000', '2001' ], + appearsIn: [ 'NEWHOPE', 'EMPIRE', 'JEDI' ], + height: 1.72, + mass: 77, + starships: [ '12', '22' ], // XXX + }, + { + id: '1001', + name: 'Darth Vader', + friends: [ '1004' ], + appearsIn: [ 'NEWHOPE', 'EMPIRE', 'JEDI' ], + height: 2.02, + mass: 136, + starships: [ '13' ], // XXX + }, + { + id: '1002', + name: 'Han Solo', + friends: [ '1000', '1003', '2001' ], + appearsIn: [ 'NEWHOPE', 'EMPIRE', 'JEDI' ], + height: 1.8, + mass: 80, + starships: [ '10', '22' ], // XXX + }, + { + id: '1003', + name: 'Leia Organa', + friends: [ '1000', '1002', '2000', '2001' ], + appearsIn: [ 'NEWHOPE', 'EMPIRE', 'JEDI' ], + height: 1.5, + mass: 49, + starships: [], + }, + { + id: '1004', + name: 'Wilhuff Tarkin', + friends: [ '1001' ], + appearsIn: [ 'NEWHOPE' ], + height: 1.8, + mass: null, + starships: [], + }, +]; + +const humanData = {}; +humans.forEach((ship) => { + humanData[ship.id] = ship; +}); + +const droids = [ + { + id: '2000', + name: 'C-3PO', + friends: [ '1000', '1002', '1003', '2001' ], + appearsIn: [ 'NEWHOPE', 'EMPIRE', 'JEDI' ], + primaryFunction: 'Protocol', + }, + { + id: '2001', + name: 'R2-D2', + friends: [ '1000', '1002', '1003' ], + appearsIn: [ 'NEWHOPE', 'EMPIRE', 'JEDI' ], + primaryFunction: 'Astromech', + }, +]; + +const droidData = {}; +droids.forEach((ship) => { + droidData[ship.id] = ship; +}); + +const starships = [ + { + id: '3000', + name: 'Millenium Falcon', + length: 34.37, + }, + { + id: '3001', + name: 'X-Wing', + length: 12.5, + }, + { + id: '3002', + name: 'TIE Advanced x1', + length: 9.2, + }, + { + id: '3003', + name: 'Imperial shuttle', + length: 20, + }, +]; + +const starshipData = {}; +starships.forEach((ship) => { + starshipData[ship.id] = ship; +}); /** * Helper function to get a character by ID. @@ -277,258 +301,28 @@ function getDroid(id) { return droidData[id]; } - -/** - * Using our shorthand to describe type systems, the type system for our - * Star Wars example is: - * - * enum Episode { NEWHOPE, EMPIRE, JEDI } - * - * interface Character { - * id: String! - * name: String - * friends: [Character] - * appearsIn: [Episode] - * } - * - * type Human : Character { - * id: String! - * name: String - * friends: [Character] - * appearsIn: [Episode] - * homePlanet: String - * } - * - * type Droid : Character { - * id: String! - * name: String - * friends: [Character] - * appearsIn: [Episode] - * primaryFunction: String - * } - * - * type Query { - * hero(episode: Episode): Character - * human(id: String!): Human - * droid(id: String!): Droid - * } - * - * We begin by setting up our schema. - */ - -/** - * The original trilogy consists of three movies. - * - * This implements the following type system shorthand: - * enum Episode { NEWHOPE, EMPIRE, JEDI } - */ -var episodeEnum = new GraphQLEnumType({ - name: 'Episode', - description: 'One of the films in the Star Wars Trilogy', - values: { - NEWHOPE: { - value: 4, - description: 'Released in 1977.', - }, - EMPIRE: { - value: 5, - description: 'Released in 1980.', - }, - JEDI: { - value: 6, - description: 'Released in 1983.', - }, - } -}); - -/** - * Characters in the Star Wars trilogy are either humans or droids. - * - * This implements the following type system shorthand: - * interface Character { - * id: String! - * name: String - * friends: [Character] - * appearsIn: [Episode] - * } - */ -var characterInterface = new GraphQLInterfaceType({ - name: 'Character', - description: 'A character in the Star Wars Trilogy', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLString), - description: 'The id of the character.', - }, - name: { - type: GraphQLString, - description: 'The name of the character.', - }, - friends: { - type: new GraphQLList(characterInterface), - description: 'The friends of the character, or an empty list if they ' + - 'have none.', - }, - appearsIn: { - type: new GraphQLList(episodeEnum), - description: 'Which movies they appear in.', - }, - }), - resolveType: character => { - return getHuman(character.id) ? humanType : droidType; - } -}); - -/** - * We define our human type, which implements the character interface. - * - * This implements the following type system shorthand: - * type Human : Character { - * id: String! - * name: String - * friends: [Character] - * appearsIn: [Episode] - * } - */ -var humanType = new GraphQLObjectType({ - name: 'Human', - description: 'A humanoid creature in the Star Wars universe.', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLString), - description: 'The id of the human.', - }, - name: { - type: GraphQLString, - description: 'The name of the human.', - }, - friends: { - type: new GraphQLList(characterInterface), - description: 'The friends of the human, or an empty list if they ' + - 'have none.', - resolve: human => getFriends(human), - }, - appearsIn: { - type: new GraphQLList(episodeEnum), - description: 'Which movies they appear in.', - }, - homePlanet: { - type: GraphQLString, - description: 'The home planet of the human, or null if unknown.', - }, - }), - interfaces: [ characterInterface ] -}); - -/** - * The other type of character in Star Wars is a droid. - * - * This implements the following type system shorthand: - * type Droid : Character { - * id: String! - * name: String - * friends: [Character] - * appearsIn: [Episode] - * primaryFunction: String - * } - */ -var droidType = new GraphQLObjectType({ - name: 'Droid', - description: 'A mechanical creature in the Star Wars universe.', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLString), - description: 'The id of the droid.', - }, - name: { - type: GraphQLString, - description: 'The name of the droid.', - }, - friends: { - type: new GraphQLList(characterInterface), - description: 'The friends of the droid, or an empty list if they ' + - 'have none.', - resolve: droid => getFriends(droid), - }, - appearsIn: { - type: new GraphQLList(episodeEnum), - description: 'Which movies they appear in.', - }, - primaryFunction: { - type: GraphQLString, - description: 'The primary function of the droid.', - }, - }), - interfaces: [ characterInterface ] -}); - -/** - * This is the type that will be the root of our query, and the - * entry point into our schema. It gives us the ability to fetch - * objects by their IDs, as well as to fetch the undisputed hero - * of the Star Wars trilogy, R2-D2, directly. - * - * This implements the following type system shorthand: - * type Query { - * hero(episode: Episode): Character - * human(id: String!): Human - * droid(id: String!): Droid - * } - * - */ -var queryType = new GraphQLObjectType({ - name: 'Query', - fields: () => ({ - hero: { - type: characterInterface, - args: { - episode: { - description: 'If omitted, returns the hero of the whole saga. If ' + - 'provided, returns the hero of that particular episode.', - type: episodeEnum - } - }, - resolve: (root, { episode }) => getHero(episode), - }, - human: { - type: humanType, - args: { - id: { - description: 'id of the human', - type: new GraphQLNonNull(GraphQLString) - } - }, - resolve: (root, { id }) => getHuman(id), - }, - droid: { - type: droidType, - args: { - id: { - description: 'id of the droid', - type: new GraphQLNonNull(GraphQLString) - } - }, - resolve: (root, { id }) => getDroid(id), - }, - }) -}); - -/** - * Finally, we construct our schema (whose starting query type is the query - * type we defined above) and export it. - */ -// export var StarWarsSchema = new GraphQLSchema({ -// query: queryType -// }); +function getStarship(id) { + return starshipData[id]; +} const resolvers = { Query: { hero: (root, { episode }) => getHero(episode), human: (root, { id }) => getHuman(id), droid: (root, { id }) => getDroid(id), - starship: () => null, + starship: (root, { id }) => getStarship(id), reviews: () => null, - search: () => null, + search: (root, { text }) => { + const re = new RegExp(text, 'i'); + + const allData = [ + ...humans, + ...droids, + ...starships, + ]; + + return allData.filter((obj) => re.test(obj.name)); + }, }, Mutation: { createReview: (root, { episode, review }) => review, @@ -555,8 +349,22 @@ const resolvers = { appearsIn: ({ appearsIn }) => appearsIn, }, Starship: { - length: () => null, - } + length: ({ length }) => length, + }, + SearchResult: { + __resolveType(data, context, info){ + if(humanData[data.id]){ + return info.schema.getType('Human'); + } + if(droidData[data.id]){ + return info.schema.getType('Droid'); + } + if(starshipData[data.id]){ + return info.schema.getType('Starship'); + } + return null; + }, + }, } /** From 208a5cdbfbcfc5d62b57c8afe62187d2414925da Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Thu, 1 Sep 2016 15:51:13 -0700 Subject: [PATCH 10/12] Fix bug from reformatting --- site/docs/_swapiSchema.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/docs/_swapiSchema.js b/site/docs/_swapiSchema.js index 2b3c516132..acc86ed3d9 100644 --- a/site/docs/_swapiSchema.js +++ b/site/docs/_swapiSchema.js @@ -281,10 +281,10 @@ function getFriends(character) { function getHero(episode) { if (episode === 'EMPIRE') { // Luke is the hero of Episode V. - return luke; + return humanData['1000']; } // Artoo is the hero otherwise. - return artoo; + return droidData['2001']; } /** From 3217239e6aadd5f3e731d4352ec030654859bce1 Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Thu, 1 Sep 2016 15:52:38 -0700 Subject: [PATCH 11/12] Fix starship ref --- site/docs/_swapiSchema.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/docs/_swapiSchema.js b/site/docs/_swapiSchema.js index acc86ed3d9..26fa925928 100644 --- a/site/docs/_swapiSchema.js +++ b/site/docs/_swapiSchema.js @@ -165,7 +165,7 @@ const humans = [ appearsIn: [ 'NEWHOPE', 'EMPIRE', 'JEDI' ], height: 1.72, mass: 77, - starships: [ '12', '22' ], // XXX + starships: [ '3001', '3003' ], }, { id: '1001', @@ -174,7 +174,7 @@ const humans = [ appearsIn: [ 'NEWHOPE', 'EMPIRE', 'JEDI' ], height: 2.02, mass: 136, - starships: [ '13' ], // XXX + starships: [ '3002' ], }, { id: '1002', @@ -183,7 +183,7 @@ const humans = [ appearsIn: [ 'NEWHOPE', 'EMPIRE', 'JEDI' ], height: 1.8, mass: 80, - starships: [ '10', '22' ], // XXX + starships: [ '3000', '3003' ], }, { id: '1003', From 2695016bf3d2ad70d9d21f85d86eb5603445f73a Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Thu, 1 Sep 2016 15:53:19 -0700 Subject: [PATCH 12/12] Add units --- site/docs/_swapiSchema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/docs/_swapiSchema.js b/site/docs/_swapiSchema.js index 26fa925928..82af8dd9d8 100644 --- a/site/docs/_swapiSchema.js +++ b/site/docs/_swapiSchema.js @@ -349,7 +349,7 @@ const resolvers = { appearsIn: ({ appearsIn }) => appearsIn, }, Starship: { - length: ({ length }) => length, + length: ({ length }) => length, // XXX add units }, SearchResult: { __resolveType(data, context, info){