generate-list-options.ts 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import {
  2. buildSchema,
  3. extendSchema,
  4. GraphQLEnumType,
  5. GraphQLField,
  6. GraphQLInputFieldConfig,
  7. GraphQLInputFieldConfigMap,
  8. GraphQLInputObjectType,
  9. GraphQLInputType,
  10. GraphQLInt,
  11. GraphQLNamedType,
  12. GraphQLObjectType,
  13. GraphQLOutputType,
  14. GraphQLSchema,
  15. isEnumType,
  16. isListType,
  17. isNonNullType,
  18. isObjectType,
  19. } from 'graphql';
  20. import { mergeSchemas } from 'graphql-tools';
  21. /**
  22. * Generates ListOptions inputs for queries which return PaginatedList types.
  23. */
  24. export function generateListOptions(typeDefsOrSchema: string | GraphQLSchema): GraphQLSchema {
  25. const schema = typeof typeDefsOrSchema === 'string' ? buildSchema(typeDefsOrSchema) : typeDefsOrSchema;
  26. const queryType = schema.getQueryType();
  27. if (!queryType) {
  28. return schema;
  29. }
  30. const objectTypes = Object.values(schema.getTypeMap()).filter(isObjectType);
  31. const allFields = objectTypes.reduce(
  32. (fields, type) => {
  33. const typeFields = Object.values(type.getFields()).filter(f => isListQueryType(f.type));
  34. return [...fields, ...typeFields];
  35. },
  36. [] as Array<GraphQLField<any, any>>,
  37. );
  38. const generatedTypes: GraphQLNamedType[] = [];
  39. for (const query of allFields) {
  40. const targetTypeName = unwrapNonNullType(query.type)
  41. .toString()
  42. .replace(/List$/, '');
  43. const targetType = schema.getType(targetTypeName);
  44. if (targetType && isObjectType(targetType)) {
  45. const sortParameter = createSortParameter(schema, targetType);
  46. const filterParameter = createFilterParameter(schema, targetType);
  47. const existingListOptions = schema.getType(
  48. `${targetTypeName}ListOptions`,
  49. ) as GraphQLInputObjectType | null;
  50. const generatedListOptions = new GraphQLInputObjectType({
  51. name: `${targetTypeName}ListOptions`,
  52. fields: {
  53. skip: { type: GraphQLInt },
  54. take: { type: GraphQLInt },
  55. sort: { type: sortParameter },
  56. filter: { type: filterParameter },
  57. ...(existingListOptions ? existingListOptions.getFields() : {}),
  58. },
  59. });
  60. let listOptionsInput: GraphQLInputObjectType;
  61. listOptionsInput = generatedListOptions;
  62. if (!query.args.find(a => a.type.toString() === `${targetTypeName}ListOptions`)) {
  63. query.args.push({
  64. name: 'options',
  65. type: listOptionsInput,
  66. });
  67. }
  68. generatedTypes.push(filterParameter);
  69. generatedTypes.push(sortParameter);
  70. generatedTypes.push(listOptionsInput);
  71. }
  72. }
  73. return mergeSchemas({ schemas: [schema, generatedTypes] });
  74. }
  75. function isListQueryType(type: GraphQLOutputType): type is GraphQLObjectType {
  76. const innerType = unwrapNonNullType(type);
  77. return isObjectType(innerType) && !!innerType.getInterfaces().find(i => i.name === 'PaginatedList');
  78. }
  79. function createSortParameter(schema: GraphQLSchema, targetType: GraphQLObjectType) {
  80. const fields = Object.values(targetType.getFields());
  81. const targetTypeName = targetType.name;
  82. const SortOrder = schema.getType('SortOrder') as GraphQLEnumType;
  83. const sortableTypes = ['ID', 'String', 'Int', 'Float', 'DateTime'];
  84. return new GraphQLInputObjectType({
  85. name: `${targetTypeName}SortParameter`,
  86. fields: fields
  87. .filter(field => sortableTypes.includes(unwrapNonNullType(field.type).name))
  88. .reduce(
  89. (result, field) => {
  90. const fieldConfig: GraphQLInputFieldConfig = {
  91. type: SortOrder,
  92. };
  93. return {
  94. ...result,
  95. [field.name]: fieldConfig,
  96. };
  97. },
  98. {} as GraphQLInputFieldConfigMap,
  99. ),
  100. });
  101. }
  102. function createFilterParameter(schema: GraphQLSchema, targetType: GraphQLObjectType): GraphQLInputObjectType {
  103. const fields = Object.values(targetType.getFields());
  104. const targetTypeName = targetType.name;
  105. const { StringOperators, BooleanOperators, NumberOperators, DateOperators } = getCommonTypes(schema);
  106. return new GraphQLInputObjectType({
  107. name: `${targetTypeName}FilterParameter`,
  108. fields: fields.reduce(
  109. (result, field) => {
  110. const filterType = getFilterType(field);
  111. if (!filterType) {
  112. return result;
  113. }
  114. const fieldConfig: GraphQLInputFieldConfig = {
  115. type: filterType,
  116. };
  117. return {
  118. ...result,
  119. [field.name]: fieldConfig,
  120. };
  121. },
  122. {} as GraphQLInputFieldConfigMap,
  123. ),
  124. });
  125. function getFilterType(field: GraphQLField<any, any>): GraphQLInputType | undefined {
  126. if (isListType(field.type)) {
  127. return;
  128. }
  129. const innerType = unwrapNonNullType(field.type);
  130. if (isEnumType(innerType)) {
  131. return StringOperators;
  132. }
  133. switch (innerType.name) {
  134. case 'String':
  135. return StringOperators;
  136. case 'Boolean':
  137. return BooleanOperators;
  138. case 'Int':
  139. case 'Float':
  140. return NumberOperators;
  141. case 'DateTime':
  142. return DateOperators;
  143. default:
  144. return;
  145. }
  146. }
  147. }
  148. function getCommonTypes(schema: GraphQLSchema) {
  149. const SortOrder = schema.getType('SortOrder') as GraphQLEnumType | null;
  150. const StringOperators = schema.getType('StringOperators') as GraphQLInputType | null;
  151. const BooleanOperators = schema.getType('BooleanOperators') as GraphQLInputType | null;
  152. const NumberRange = schema.getType('NumberRange') as GraphQLInputType | null;
  153. const NumberOperators = schema.getType('NumberOperators') as GraphQLInputType | null;
  154. const DateRange = schema.getType('DateRange') as GraphQLInputType | null;
  155. const DateOperators = schema.getType('DateOperators') as GraphQLInputType | null;
  156. if (
  157. !SortOrder ||
  158. !StringOperators ||
  159. !BooleanOperators ||
  160. !NumberRange ||
  161. !NumberOperators ||
  162. !DateRange ||
  163. !DateOperators
  164. ) {
  165. throw new Error(`A common type was not defined`);
  166. }
  167. return {
  168. SortOrder,
  169. StringOperators,
  170. BooleanOperators,
  171. NumberOperators,
  172. DateOperators,
  173. };
  174. }
  175. /**
  176. * Unwraps the inner type if it is inside a non-nullable type
  177. */
  178. function unwrapNonNullType(type: GraphQLOutputType): GraphQLNamedType {
  179. if (isNonNullType(type)) {
  180. return type.ofType;
  181. }
  182. return type;
  183. }