Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add reference types #3995

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
61 changes: 61 additions & 0 deletions src/__tests__/starWarsData.ts
Expand Up @@ -27,6 +27,22 @@ export interface Droid {
primaryFunction: string;
}

export interface Jedi {
type: 'Jedi';
id: string;
human: string;
lightsaberColor: string;
enemies: ReadonlyArray<string>;
}

export interface Sith {
type: 'Sith';
id: string;
human: string;
darkSidePower: string;
enemies: ReadonlyArray<string>;
}

/**
* This defines a basic set of data for our Star Wars Schema.
*
Expand Down Expand Up @@ -109,6 +125,30 @@ const droidData: { [id: string]: Droid } = {
[artoo.id]: artoo,
};

const jediLuke: Jedi = {
type: 'Jedi',
id: '3000',
human: '1000',
lightsaberColor: 'Blue',
enemies: ['4000'],
};

const jediData: { [id: string]: Jedi } = {
[jediLuke.id]: jediLuke,
};

const sithVader: Sith = {
type: 'Sith',
id: '4000',
human: '1001',
darkSidePower: 'Force Choke',
enemies: ['3000'],
};

const sithData: { [id: string]: Sith } = {
[sithVader.id]: sithVader,
};

/**
* Helper function to get a character by ID.
*/
Expand Down Expand Up @@ -152,3 +192,24 @@ export function getHuman(id: string): Human | null {
export function getDroid(id: string): Droid | null {
return droidData[id];
}

/**
* Allows us to query for the jedi with the given id
*/
export function getJedi(id: string): Jedi | null {
return jediData[id];
}

/**
* Allows us to query for the enimies.
*/
export function getEnemies(character: Jedi | Sith): Array<Jedi | Sith | null> {
return character.enemies.map((id) => getJedi(id) ?? getSith(id));
}

/**
* Allows us to query for the sith with the given id
*/
export function getSith(id: string): Sith | null {
return sithData[id];
}
38 changes: 38 additions & 0 deletions src/__tests__/starWarsIntrospection-test.ts
Expand Up @@ -35,6 +35,8 @@ describe('Star Wars Introspection Tests', () => {
{ name: 'String' },
{ name: 'Episode' },
{ name: 'Droid' },
{ name: 'Jedi' },
{ name: 'Sith' },
{ name: 'Query' },
{ name: 'Boolean' },
{ name: '__Schema' },
Expand Down Expand Up @@ -339,6 +341,42 @@ describe('Star Wars Introspection Tests', () => {
},
],
},
{
name: 'jedi',
args: [
{
name: 'id',
description: 'id of the jedi',
type: {
kind: 'NON_NULL',
name: null,
ofType: {
kind: 'SCALAR',
name: 'String',
},
},
defaultValue: null,
},
],
},
{
name: 'sith',
args: [
{
name: 'id',
description: 'id of the sith',
type: {
kind: 'NON_NULL',
name: null,
ofType: {
kind: 'SCALAR',
name: 'String',
},
},
defaultValue: null,
},
],
},
],
},
},
Expand Down
56 changes: 56 additions & 0 deletions src/__tests__/starWarsQuery-test.ts
Expand Up @@ -494,4 +494,60 @@ describe('Star Wars Query Tests', () => {
});
});
});

describe('Using types with Ref types', () => {
it('Allows us to query jedi enemies', async () => {
const source = `
query FetchJediEnemies {
jedi(id: "3000") {
enemies {
id
darkSidePower
}
}
}
`;

const result = await graphql({ schema, source });
expect(result).to.deep.equal({
data: {
jedi: {
enemies: [
{
id: '4000',
darkSidePower: 'Force Choke',
},
],
},
},
});
});

it('Allows us to query sith enemies', async () => {
const source = `
query FetchSithEnemies {
sith(id: "4000") {
enemies {
id
lightsaberColor
}
}
}
`;

const result = await graphql({ schema, source });
expect(result).to.deep.equal({
data: {
sith: {
enemies: [
{
id: '3000',
lightsaberColor: 'Blue',
},
],
},
},
});
});
});
});
114 changes: 112 additions & 2 deletions src/__tests__/starWarsSchema.ts
Expand Up @@ -8,7 +8,15 @@ import {
import { GraphQLString } from '../type/scalars.js';
import { GraphQLSchema } from '../type/schema.js';

import { getDroid, getFriends, getHero, getHuman } from './starWarsData.js';
import {
getDroid,
getEnemies,
getFriends,
getHero,
getHuman,
getJedi,
getSith,
} from './starWarsData.js';

/**
* This is designed to be an end-to-end test, demonstrating
Expand Down Expand Up @@ -241,6 +249,86 @@ const droidType = new GraphQLObjectType({
interfaces: [characterInterface],
});

/**
* We introduce two new types to represent characters in the Star Wars universe:
* Jedi and Sith. Both types have a non-null array field 'enemies' that references
* characters of the opposite type, creating a bidirectional relationship.
*
* This implements the following type system shorthand:
* ```graphql
* type Jedi {
* id: String!
* human: Human!
* lightsaberColor: String
* enemies: [Sith!]!
* }
*
* type Sith {
* id: String!
* human: Human!
* darkSidePower: String
* enemies: [Jedi!]!
* }
* ```
*/
const jediType = new GraphQLObjectType({
name: 'Jedi',
description: 'A Jedi character in the Star Wars universe.',
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLString),
description: 'The id of the Jedi.',
},
name: {
type: GraphQLString,
description: 'The name of the Jedi.',
},
human: {
type: new GraphQLNonNull(humanType),
description: 'The human counterpart of the Jedi.',
resolve: (jedi) => getHuman(jedi.human),
},
lightsaberColor: {
type: GraphQLString,
description: "The color of the Jedi's lightsaber.",
},
enemies: {
type: new GraphQLList('Sith'),
description: 'The enemies of the Jedi.',
resolve: (sith) => getEnemies(sith),
},
}),
});

const sithType = new GraphQLObjectType({
name: 'Sith',
description: 'A Sith character in the Star Wars universe.',
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLString),
description: 'The id of the Sith.',
},
name: {
type: GraphQLString,
description: 'The name of the Sith.',
},
human: {
type: new GraphQLNonNull(humanType),
description: 'The human counterpart of the Jedi.',
resolve: (sith) => getHuman(sith.human),
},
darkSidePower: {
type: GraphQLString,
description: 'The dark side power of the Sith.',
},
enemies: {
type: new GraphQLList('Jedi'),
description: 'The enemies of the Sith.',
resolve: (sith) => getEnemies(sith),
},
}),
});

/**
* 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
Expand All @@ -253,6 +341,8 @@ const droidType = new GraphQLObjectType({
* hero(episode: Episode): Character
* human(id: String!): Human
* droid(id: String!): Droid
* jedi(id: String!): Jedi
* sith(id: String!): Sith
* }
* ```
*/
Expand Down Expand Up @@ -290,6 +380,26 @@ const queryType = new GraphQLObjectType({
},
resolve: (_source, { id }) => getDroid(id),
},
jedi: {
type: jediType,
args: {
id: {
description: 'id of the jedi',
type: new GraphQLNonNull(GraphQLString),
},
},
resolve: (_source, { id }) => getJedi(id),
},
sith: {
type: sithType,
args: {
id: {
description: 'id of the sith',
type: new GraphQLNonNull(GraphQLString),
},
},
resolve: (_source, { id }) => getSith(id),
},
}),
});

Expand All @@ -299,5 +409,5 @@ const queryType = new GraphQLObjectType({
*/
export const StarWarsSchema: GraphQLSchema = new GraphQLSchema({
query: queryType,
types: [humanType, droidType],
types: [humanType, droidType, jediType, sithType],
});