schema.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import {
  2. GraphQLBoolean,
  3. GraphQLFloat,
  4. GraphQLID,
  5. GraphQLInt,
  6. GraphQLList,
  7. GraphQLNonNull,
  8. GraphQLObjectType,
  9. GraphQLSchema,
  10. GraphQLString,
  11. GraphQLInputObjectType,
  12. } from 'graphql';
  13. import {
  14. mapValues,
  15. mapKeys,
  16. } from 'lodash';
  17. import {
  18. schema
  19. } from './swapi.js';
  20. import rp from 'request-promise';
  21. import DataLoader from 'dataloader';
  22. // Generate a GraphQL object type for every entry in the schema
  23. const graphQLObjectTypes = mapValues(schema, (jsonSchema) => {
  24. return new GraphQLObjectType({
  25. name: jsonSchema.title,
  26. description: jsonSchema.description,
  27. fields: () => {
  28. return mapValues(jsonSchema.properties, (propertySchema, propertyName) => {
  29. const value = {
  30. description: propertySchema.description,
  31. type: jsonSchemaTypeToGraphQL(propertySchema.type, propertyName)
  32. };
  33. // All of the arrays in the schema happen to be references to other types
  34. if(propertySchema.type === 'array') {
  35. value.resolve = (root, args) => {
  36. const arrayOfUrls = root[propertyName];
  37. const arrayOfResults = arrayOfUrls.map(restLoader.load.bind(restLoader));
  38. return arrayOfResults;
  39. }
  40. }
  41. return value;
  42. });
  43. }
  44. })
  45. });
  46. // Convert the JSON Schema types to the actual GraphQL types in our schema
  47. function jsonSchemaTypeToGraphQL(jsonSchemaType, schemaName) {
  48. if (jsonSchemaType === "array") {
  49. if (graphQLObjectTypes[schemaName]) {
  50. return new GraphQLList(graphQLObjectTypes[schemaName]);
  51. } else {
  52. const translated = {
  53. pilots: "people",
  54. characters: "people",
  55. residents: "people"
  56. }[schemaName];
  57. if (! translated) {
  58. throw new Error(`no type ${schemaName}`);
  59. }
  60. const type = graphQLObjectTypes[translated];
  61. if (! type) {
  62. throw new Error(`no GraphQL type ${schemaName}`);
  63. }
  64. return new GraphQLList(type);
  65. }
  66. }
  67. return {
  68. string: GraphQLString,
  69. date: GraphQLString,
  70. integer: GraphQLInt,
  71. }[jsonSchemaType];
  72. }
  73. const queryType = new GraphQLObjectType({
  74. name: 'Query',
  75. fields: () => {
  76. // For each type, make a query to get a page of that type
  77. const plural = mapValues(graphQLObjectTypes, (type, typePluralName) => {
  78. return {
  79. type: new GraphQLList(type),
  80. description: `All ${typePluralName}.`,
  81. args: {
  82. page: { type: GraphQLInt }
  83. },
  84. resolve: (_, { page }) => {
  85. // Simple pagination, where you just pass the page number through to the REST API
  86. return fetchPageOfType(typePluralName, page);
  87. },
  88. };
  89. });
  90. // For each type, also make a query to get just one object of that type
  91. const singular = mapValues(mapKeys(graphQLObjectTypes, (value, key) => {
  92. // Name the query people_one vehicles_one etc. not sure what the standard should be
  93. // here. We could also adopt the Relay node spec
  94. return key + "_one";
  95. }), (type, typeQueryName) => {
  96. const restName = typeQueryName.split('_')[0];
  97. return {
  98. type: type,
  99. description: `One ${restName}.`,
  100. args: {
  101. id: { type: GraphQLString }
  102. },
  103. resolve: (_, { id }) => {
  104. return fetchOne(restName, id);
  105. },
  106. }
  107. });
  108. return {
  109. ...plural,
  110. ...singular
  111. };
  112. },
  113. });
  114. // A helper to unwrap the paginated object from SWAPI
  115. function fetchPageOfType(typePluralName, pageNumber) {
  116. let url = `http://swapi.co/api/${typePluralName}/`;
  117. if (pageNumber) {
  118. url += `?page=${pageNumber}`;
  119. };
  120. return restLoader.load(url).then((data) => {
  121. // Paginated results have a different shape
  122. return data.results;
  123. });
  124. }
  125. // Constructs a URL from the endpoint name and object ID
  126. function fetchOne(restName, id) {
  127. return restLoader.load(`http://swapi.co/api/${restName}/${id}`);
  128. }
  129. // Use dataloader to batch URL requests for each layer of the query
  130. const restLoader = new DataLoader((urls) => {
  131. console.log("Fetching batch:", urls);
  132. return Promise.all(urls.map((url) => {
  133. return rp({
  134. uri: url,
  135. json: true
  136. });
  137. }));
  138. });
  139. // This schema has no mutations because you can't really write to the Star Wars API.
  140. export const Schema = new GraphQLSchema({
  141. query: queryType,
  142. });