|
|
@@ -0,0 +1,194 @@
|
|
|
+import {
|
|
|
+ buildSchema,
|
|
|
+ extendSchema,
|
|
|
+ GraphQLEnumType,
|
|
|
+ GraphQLField,
|
|
|
+ GraphQLInputFieldConfig,
|
|
|
+ GraphQLInputFieldConfigMap,
|
|
|
+ GraphQLInputObjectType,
|
|
|
+ GraphQLInputType,
|
|
|
+ GraphQLInt,
|
|
|
+ GraphQLNamedType,
|
|
|
+ GraphQLObjectType,
|
|
|
+ GraphQLOutputType,
|
|
|
+ GraphQLSchema,
|
|
|
+ isEnumType,
|
|
|
+ isListType,
|
|
|
+ isNonNullType,
|
|
|
+ isObjectType,
|
|
|
+} from 'graphql';
|
|
|
+import { mergeSchemas } from 'graphql-tools';
|
|
|
+
|
|
|
+/**
|
|
|
+ * Generates ListOptions inputs for queries which return PaginatedList types.
|
|
|
+ */
|
|
|
+export function generateListOptions(typeDefsOrSchema: string | GraphQLSchema): GraphQLSchema {
|
|
|
+ const schema = typeof typeDefsOrSchema === 'string' ? buildSchema(typeDefsOrSchema) : typeDefsOrSchema;
|
|
|
+ const queryType = schema.getQueryType();
|
|
|
+ if (!queryType) {
|
|
|
+ return schema;
|
|
|
+ }
|
|
|
+ const queries = queryType.getFields();
|
|
|
+ const { SortOrder, StringOperators, BooleanOperators, NumberOperators, DateOperators } = getCommonTypes(
|
|
|
+ schema,
|
|
|
+ );
|
|
|
+ const generatedTypes: GraphQLNamedType[] = [];
|
|
|
+
|
|
|
+ for (const query of Object.values(queries)) {
|
|
|
+ const type = isNonNullType(query.type) ? query.type.ofType : query.type;
|
|
|
+ const isListQuery =
|
|
|
+ isObjectType(type) && !!type.getInterfaces().find(i => i.name === 'PaginatedList');
|
|
|
+
|
|
|
+ if (isListQuery) {
|
|
|
+ const targetTypeName = type.toString().replace(/List$/, '');
|
|
|
+ const targetType = schema.getType(targetTypeName);
|
|
|
+ if (targetType && isObjectType(targetType)) {
|
|
|
+ const sortParameter = createSortParameter(schema, targetType);
|
|
|
+ const filterParameter = createFilterParameter(schema, targetType);
|
|
|
+ const existingListOptions = schema.getType(
|
|
|
+ `${targetTypeName}ListOptions`,
|
|
|
+ ) as GraphQLInputObjectType | null;
|
|
|
+ const generatedListOptions = new GraphQLInputObjectType({
|
|
|
+ name: `${targetTypeName}ListOptions`,
|
|
|
+ fields: {
|
|
|
+ skip: { type: GraphQLInt },
|
|
|
+ take: { type: GraphQLInt },
|
|
|
+ sort: { type: sortParameter },
|
|
|
+ filter: { type: filterParameter },
|
|
|
+ ...(existingListOptions ? existingListOptions.getFields() : {}),
|
|
|
+ },
|
|
|
+ });
|
|
|
+ let listOptionsInput: GraphQLInputObjectType;
|
|
|
+ if (existingListOptions) {
|
|
|
+ generatedTypes.push(existingListOptions);
|
|
|
+ }
|
|
|
+
|
|
|
+ listOptionsInput = generatedListOptions;
|
|
|
+
|
|
|
+ if (!query.args.find(a => a.type.toString() === `${targetTypeName}ListOptions`)) {
|
|
|
+ query.args.push({
|
|
|
+ name: 'options',
|
|
|
+ type: listOptionsInput,
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ generatedTypes.push(filterParameter);
|
|
|
+ generatedTypes.push(sortParameter);
|
|
|
+ generatedTypes.push(listOptionsInput);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return mergeSchemas({ schemas: [schema, generatedTypes] });
|
|
|
+}
|
|
|
+
|
|
|
+function createSortParameter(schema: GraphQLSchema, targetType: GraphQLObjectType) {
|
|
|
+ const fields = Object.values(targetType.getFields());
|
|
|
+ const targetTypeName = targetType.name;
|
|
|
+ const SortOrder = schema.getType('SortOrder') as GraphQLEnumType;
|
|
|
+
|
|
|
+ const sortableTypes = ['ID', 'String', 'Int', 'Float', 'DateTime'];
|
|
|
+ return new GraphQLInputObjectType({
|
|
|
+ name: `${targetTypeName}SortParameter`,
|
|
|
+ fields: fields
|
|
|
+ .filter(field => sortableTypes.includes(unwrapNonNullType(field.type).name))
|
|
|
+ .reduce(
|
|
|
+ (result, field) => {
|
|
|
+ const fieldConfig: GraphQLInputFieldConfig = {
|
|
|
+ type: SortOrder,
|
|
|
+ };
|
|
|
+ return {
|
|
|
+ ...result,
|
|
|
+ [field.name]: fieldConfig,
|
|
|
+ };
|
|
|
+ },
|
|
|
+ {} as GraphQLInputFieldConfigMap,
|
|
|
+ ),
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function createFilterParameter(schema: GraphQLSchema, targetType: GraphQLObjectType): GraphQLInputObjectType {
|
|
|
+ const fields = Object.values(targetType.getFields());
|
|
|
+ const targetTypeName = targetType.name;
|
|
|
+ const { StringOperators, BooleanOperators, NumberOperators, DateOperators } = getCommonTypes(schema);
|
|
|
+
|
|
|
+ return new GraphQLInputObjectType({
|
|
|
+ name: `${targetTypeName}FilterParameter`,
|
|
|
+ fields: fields.reduce(
|
|
|
+ (result, field) => {
|
|
|
+ const filterType = getFilterType(field);
|
|
|
+ if (!filterType) {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ const fieldConfig: GraphQLInputFieldConfig = {
|
|
|
+ type: filterType,
|
|
|
+ };
|
|
|
+ return {
|
|
|
+ ...result,
|
|
|
+ [field.name]: fieldConfig,
|
|
|
+ };
|
|
|
+ },
|
|
|
+ {} as GraphQLInputFieldConfigMap,
|
|
|
+ ),
|
|
|
+ });
|
|
|
+
|
|
|
+ function getFilterType(field: GraphQLField<any, any>): GraphQLInputType | undefined {
|
|
|
+ if (isListType(field.type)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const innerType = unwrapNonNullType(field.type);
|
|
|
+ if (isEnumType(innerType)) {
|
|
|
+ return StringOperators;
|
|
|
+ }
|
|
|
+ switch (innerType.name) {
|
|
|
+ case 'String':
|
|
|
+ return StringOperators;
|
|
|
+ case 'Boolean':
|
|
|
+ return BooleanOperators;
|
|
|
+ case 'Int':
|
|
|
+ case 'Float':
|
|
|
+ return NumberOperators;
|
|
|
+ case 'DateTime':
|
|
|
+ return DateOperators;
|
|
|
+ default:
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function getCommonTypes(schema: GraphQLSchema) {
|
|
|
+ const SortOrder = schema.getType('SortOrder') as GraphQLEnumType | null;
|
|
|
+ const StringOperators = schema.getType('StringOperators') as GraphQLInputType | null;
|
|
|
+ const BooleanOperators = schema.getType('BooleanOperators') as GraphQLInputType | null;
|
|
|
+ const NumberRange = schema.getType('NumberRange') as GraphQLInputType | null;
|
|
|
+ const NumberOperators = schema.getType('NumberOperators') as GraphQLInputType | null;
|
|
|
+ const DateRange = schema.getType('DateRange') as GraphQLInputType | null;
|
|
|
+ const DateOperators = schema.getType('DateOperators') as GraphQLInputType | null;
|
|
|
+ if (
|
|
|
+ !SortOrder ||
|
|
|
+ !StringOperators ||
|
|
|
+ !BooleanOperators ||
|
|
|
+ !NumberRange ||
|
|
|
+ !NumberOperators ||
|
|
|
+ !DateRange ||
|
|
|
+ !DateOperators
|
|
|
+ ) {
|
|
|
+ throw new Error(`A common type was not defined`);
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ SortOrder,
|
|
|
+ StringOperators,
|
|
|
+ BooleanOperators,
|
|
|
+ NumberOperators,
|
|
|
+ DateOperators,
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Unwraps the inner type if it is inside a non-nullable type
|
|
|
+ */
|
|
|
+function unwrapNonNullType(type: GraphQLOutputType): GraphQLNamedType {
|
|
|
+ if (isNonNullType(type)) {
|
|
|
+ return type.ofType;
|
|
|
+ }
|
|
|
+ return type;
|
|
|
+}
|