js_react_apollo__abhiaiyer/server/schema.js

165 lines
4.2 KiB
JavaScript

import {
GraphQLBoolean,
GraphQLFloat,
GraphQLID,
GraphQLInt,
GraphQLList,
GraphQLNonNull,
GraphQLObjectType,
GraphQLSchema,
GraphQLString,
GraphQLInputObjectType,
} from 'graphql';
import {
mapValues,
mapKeys,
} from 'lodash';
import {
schema
} from './swapi.js';
import rp from 'request-promise';
import DataLoader from 'dataloader';
// Generate a GraphQL object type for every entry in the schema
const graphQLObjectTypes = mapValues(schema, (jsonSchema) => {
return new GraphQLObjectType({
name: jsonSchema.title,
description: jsonSchema.description,
fields: () => {
return mapValues(jsonSchema.properties, (propertySchema, propertyName) => {
const value = {
description: propertySchema.description,
type: jsonSchemaTypeToGraphQL(propertySchema.type, propertyName)
};
// All of the arrays in the schema happen to be references to other types
if(propertySchema.type === 'array') {
value.resolve = (root, args) => {
const arrayOfUrls = root[propertyName];
const arrayOfResults = arrayOfUrls.map(restLoader.load.bind(restLoader));
return arrayOfResults;
}
}
return value;
});
}
})
});
// Convert the JSON Schema types to the actual GraphQL types in our schema
function jsonSchemaTypeToGraphQL(jsonSchemaType, schemaName) {
if (jsonSchemaType === "array") {
if (graphQLObjectTypes[schemaName]) {
return new GraphQLList(graphQLObjectTypes[schemaName]);
} else {
const translated = {
pilots: "people",
characters: "people",
residents: "people"
}[schemaName];
if (! translated) {
throw new Error(`no type ${schemaName}`);
}
const type = graphQLObjectTypes[translated];
if (! type) {
throw new Error(`no GraphQL type ${schemaName}`);
}
return new GraphQLList(type);
}
}
return {
string: GraphQLString,
date: GraphQLString,
integer: GraphQLInt,
}[jsonSchemaType];
}
const queryType = new GraphQLObjectType({
name: 'Query',
fields: () => {
// For each type, make a query to get a page of that type
const plural = mapValues(graphQLObjectTypes, (type, typePluralName) => {
return {
type: new GraphQLList(type),
description: `All ${typePluralName}.`,
args: {
page: { type: GraphQLInt }
},
resolve: (_, { page }) => {
// Simple pagination, where you just pass the page number through to the REST API
return fetchPageOfType(typePluralName, page);
},
};
});
// For each type, also make a query to get just one object of that type
const singular = mapValues(mapKeys(graphQLObjectTypes, (value, key) => {
// Name the query people_one vehicles_one etc. not sure what the standard should be
// here. We could also adopt the Relay node spec
return key + "_one";
}), (type, typeQueryName) => {
const restName = typeQueryName.split('_')[0];
return {
type: type,
description: `One ${restName}.`,
args: {
id: { type: GraphQLString }
},
resolve: (_, { id }) => {
return fetchOne(restName, id);
},
}
});
return {
...plural,
...singular
};
},
});
// A helper to unwrap the paginated object from SWAPI
function fetchPageOfType(typePluralName, pageNumber) {
let url = `http://swapi.co/api/${typePluralName}/`;
if (pageNumber) {
url += `?page=${pageNumber}`;
};
return restLoader.load(url).then((data) => {
// Paginated results have a different shape
return data.results;
});
}
// Constructs a URL from the endpoint name and object ID
function fetchOne(restName, id) {
return restLoader.load(`http://swapi.co/api/${restName}/${id}`);
}
// Use dataloader to batch URL requests for each layer of the query
const restLoader = new DataLoader((urls) => {
console.log("Fetching batch:", urls);
return Promise.all(urls.map((url) => {
return rp({
uri: url,
json: true
});
}));
});
// This schema has no mutations because you can't really write to the Star Wars API.
export const Schema = new GraphQLSchema({
query: queryType,
});