graphql#GraphQLBoolean TypeScript Examples

The following examples show how to use graphql#GraphQLBoolean. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: utils.ts    From proto2graphql with MIT License 7 votes vote down vote up
ScalarTypeMap = {
  double: GraphQLFloat,
  float: GraphQLFloat,
  int32: GraphQLInt,
  int64: GraphQLInt,
  uint32: GraphQLInt,
  uint64: GraphQLInt,
  sint32: GraphQLInt,
  sint64: GraphQLInt,
  fixed32: GraphQLInt,
  fixed64: GraphQLInt,
  sfixed32: GraphQLInt,
  sfixed64: GraphQLInt,
  bool: GraphQLBoolean,
  string: GraphQLString,
  bytes: GraphQLString
}
Example #2
Source File: withOperators.ts    From payload with MIT License 6 votes vote down vote up
withOperators = (field: FieldAffectingData, type: GraphQLType, parentName: string, operators: string[]): GraphQLInputObjectType => {
  const name = `${combineParentName(parentName, field.name)}_operator`;
  const listOperators = ['in', 'not_in', 'all'];

  if (!field.required) operators.push('exists');

  return new GraphQLInputObjectType({
    name,
    fields: operators.reduce((fields, operator) => {
      let gqlType: GraphQLType;
      if (listOperators.indexOf(operator) > -1) {
        gqlType = new GraphQLList(type);
      } else if (operator === 'exists') {
        gqlType = GraphQLBoolean;
      } else {
        gqlType = type;
      }
      return {
        ...fields,
        [operator]: {
          type: gqlType,
        },
      };
    }, {}),
  });
}
Example #3
Source File: buildPoliciesType.ts    From payload with MIT License 6 votes vote down vote up
buildEntity = (label: string, entityFields: Field[], operations: OperationType[]) => {
  const formattedLabel = formatName(label);

  const fields = {
    fields: {
      type: new GraphQLObjectType({
        name: formatName(`${formattedLabel}Fields`),
        fields: buildFields(`${formattedLabel}Fields`, entityFields),
      }),
    },
  };

  operations.forEach((operation) => {
    const capitalizedOperation = operation.charAt(0).toUpperCase() + operation.slice(1);

    fields[operation] = {
      type: new GraphQLObjectType({
        name: `${formattedLabel}${capitalizedOperation}Access`,
        fields: {
          permission: { type: new GraphQLNonNull(GraphQLBoolean) },
          where: { type: GraphQLJSONObject },
        },
      }),
    };
  });

  return fields;
}
Example #4
Source File: buildPaginatedListType.ts    From payload with MIT License 6 votes vote down vote up
buildPaginatedListType = (name, docType) => new GraphQLObjectType({
  name,
  fields: {
    docs: {
      type: new GraphQLList(docType),
    },
    totalDocs: { type: GraphQLInt },
    offset: { type: GraphQLInt },
    limit: { type: GraphQLInt },
    totalPages: { type: GraphQLInt },
    page: { type: GraphQLInt },
    pagingCounter: { type: GraphQLInt },
    hasPrevPage: { type: GraphQLBoolean },
    hasNextPage: { type: GraphQLBoolean },
    prevPage: { type: GraphQLBoolean },
    nextPage: { type: GraphQLBoolean },
  },
})
Example #5
Source File: GraphqlTypeInterpreter.test.ts    From aloxide with Apache License 2.0 6 votes vote down vote up
describe('test GraphqlTypeInterpreter', () => {
  describe('interpret()', () => {
    const intepreter = new GraphqlTypeInterpreter();

    it('should throw error when Field Type is not support', () => {
      expect(() => {
        // @ts-ignore
        intepreter.interpret('not supported type');
      }).toThrowError('unknow type');
    });

    it('should return Graphql Int when the input is uint', () => {
      expect(intepreter.interpret(FieldTypeEnum.uint16_t)).toBe(GraphQLInt);
      expect(intepreter.interpret(FieldTypeEnum.uint32_t)).toBe(GraphQLInt);
      expect(intepreter.interpret(FieldTypeEnum.uint64_t)).toBe(GraphQLInt);
    });

    it('should return Graphql Float when the input is number or double', () => {
      expect(intepreter.interpret(FieldTypeEnum.number)).toBe(GraphQLFloat);
      expect(intepreter.interpret(FieldTypeEnum.double)).toBe(GraphQLFloat);
    });

    it('should return Graphql Boolean when the input is boolean', () => {
      expect(intepreter.interpret(FieldTypeEnum.bool)).toBe(GraphQLBoolean);
    });

    it('should return Graphql String when the input is string', () => {
      expect(intepreter.interpret(FieldTypeEnum.account)).toBe(GraphQLString);
      expect(intepreter.interpret(FieldTypeEnum.string)).toBe(GraphQLString);
    });
  });
});
Example #6
Source File: GraphqlTypeInterpreter.ts    From aloxide with Apache License 2.0 6 votes vote down vote up
interpret(input: FieldTypeEnum): GraphQLScalarType {
    let type: GraphQLScalarType;

    switch (input) {
      case FieldTypeEnum.uint16_t:
      case FieldTypeEnum.uint32_t:
      case FieldTypeEnum.uint64_t:
        type = GraphQLInt;
        break;
      case FieldTypeEnum.number:
      case FieldTypeEnum.double:
        type = GraphQLFloat;
        break;
      case FieldTypeEnum.bool:
        type = GraphQLBoolean;
        break;
      case FieldTypeEnum.account:
      case FieldTypeEnum.string:
        type = GraphQLString;
        break;
      default:
        throw new Error(`unknow type ${type}`);
    }

    return type;
  }
Example #7
Source File: schema.ts    From Deep-Lynx with MIT License 5 votes vote down vote up
// each key in the metatype should be included on the input object as a field to be filtered on
    inputFieldsForMetatype(metatype: Metatype): {[key: string]: any} {
        const fields: {[key: string]: any} = {};

        metatype.keys?.forEach((metatypeKey) => {
            const propertyName = stringToValidPropertyName(metatypeKey.property_name);

            switch (metatypeKey.data_type) {
                // because we have no specification on our internal number type, we
                // must set this as a float for now
                case 'number': {
                    fields[propertyName] = {
                        type: GraphQLFloat,
                    };
                    break;
                }

                case 'boolean': {
                    fields[propertyName] = {
                        type: GraphQLBoolean,
                    };
                    break;
                }

                case 'string' || 'date' || 'file': {
                    fields[propertyName] = {
                        type: GraphQLString,
                    };
                    break;
                }

                case 'list': {
                    fields[propertyName] = {
                        type: new GraphQLList(GraphQLJSON),
                    };
                    break;
                }

                case 'enumeration': {
                    const enumMap: {[key: string]: GraphQLEnumValueConfig} = {};

                    if (metatypeKey.options) {
                        metatypeKey.options.forEach((option) => {
                            enumMap[option] = {
                                value: option,
                            };
                        });
                    }

                    // we have to include a UUID here so that we can insure a uniquely named type
                    fields[propertyName] = {
                        type: new GraphQLEnumType({
                            name: stringToValidPropertyName(`${metatype.name}_${metatypeKey.name}_Enum_Type_B`),
                            values: enumMap,
                        }),
                    };
                    break;
                }

                default: {
                    fields[propertyName] = {
                        type: GraphQLString,
                    };
                }
            }
        });

        return fields;
    }
Example #8
Source File: buildPoliciesType.ts    From payload with MIT License 5 votes vote down vote up
buildFields = (label, fieldsToBuild) => fieldsToBuild.reduce((builtFields, field) => {
  if (!field.hidden) {
    if (field.name) {
      const fieldName = formatName(field.name);

      const objectTypeFields: ObjectTypeFields = ['create', 'read', 'update', 'delete'].reduce((operations, operation) => {
        const capitalizedOperation = operation.charAt(0).toUpperCase() + operation.slice(1);

        return {
          ...operations,
          [operation]: {
            type: new GraphQLObjectType({
              name: `${label}_${fieldName}_${capitalizedOperation}`,
              fields: {
                permission: {
                  type: new GraphQLNonNull(GraphQLBoolean),
                },
              },
            }),
          },
        };
      }, {});

      if (field.fields) {
        objectTypeFields.fields = {
          type: new GraphQLObjectType({
            name: `${label}_${fieldName}_Fields`,
            fields: buildFields(`${label}_${fieldName}`, field.fields),
          }),
        };
      }

      return {
        ...builtFields,
        [field.name]: {
          type: new GraphQLObjectType({
            name: `${label}_${fieldName}`,
            fields: objectTypeFields,
          }),
        },
      };
    }

    if (!field.name && field.fields) {
      const subFields = buildFields(label, field.fields);

      return {
        ...builtFields,
        ...subFields,
      };
    }
  }
  return builtFields;
}, {})
Example #9
Source File: buildPoliciesType.ts    From payload with MIT License 5 votes vote down vote up
export default function buildPoliciesType(): GraphQLObjectType {
  const fields = {
    canAccessAdmin: {
      type: new GraphQLNonNull(GraphQLBoolean),
    },
  };

  Object.values(this.config.collections).forEach((collection: SanitizedCollectionConfig) => {
    const collectionOperations: OperationType[] = ['create', 'read', 'update', 'delete'];

    if (collection.auth && (typeof collection.auth.maxLoginAttempts !== 'undefined' && collection.auth.maxLoginAttempts !== 0)) {
      collectionOperations.push('unlock');
    }

    if (collection.versions) {
      collectionOperations.push('readVersions');
    }

    fields[formatName(collection.slug)] = {
      type: new GraphQLObjectType({
        name: formatName(`${collection.labels.singular}Access`),
        fields: buildEntity(collection.labels.singular, collection.fields, collectionOperations),
      }),
    };
  });

  Object.values(this.config.globals).forEach((global: SanitizedGlobalConfig) => {
    const globalOperations: OperationType[] = ['read', 'update'];

    if (global.versions) {
      globalOperations.push('readVersions');
    }

    fields[formatName(global.slug)] = {
      type: new GraphQLObjectType({
        name: formatName(`${global.label}Access`),
        fields: buildEntity(global.label, global.fields, globalOperations),
      }),
    };
  });

  return new GraphQLObjectType({
    name: 'Access',
    fields,
  });
}
Example #10
Source File: schema.ts    From Deep-Lynx with MIT License 5 votes vote down vote up
// each key in the relationship should be included on the input object as a field to be filtered on
    inputFieldsForRelationship(relationship: MetatypeRelationship): {[key: string]: any} {
        const fields: {[key: string]: any} = {};

        relationship.keys?.forEach((relationshipKey) => {
            const propertyName = stringToValidPropertyName(relationshipKey.property_name);

            switch (relationshipKey.data_type) {
                // because we have no specification on our internal number type, we
                // must set this as a float for now
                case 'number': {
                    fields[propertyName] = {
                        type: GraphQLFloat,
                    };
                    break;
                }

                case 'boolean': {
                    fields[propertyName] = {
                        type: GraphQLBoolean,
                    };
                    break;
                }

                case 'string' || 'date' || 'file': {
                    fields[propertyName] = {
                        type: GraphQLString,
                    };
                    break;
                }

                case 'list': {
                    fields[propertyName] = {
                        type: new GraphQLList(GraphQLJSON),
                    };
                    break;
                }

                case 'enumeration': {
                    const enumMap: {[key: string]: GraphQLEnumValueConfig} = {};

                    if (relationshipKey.options) {
                        relationshipKey.options.forEach((option) => {
                            enumMap[option] = {
                                value: option,
                            };
                        });
                    }

                    fields[propertyName] = {
                        type: new GraphQLEnumType({
                            name: stringToValidPropertyName(`${relationship.name}_${relationshipKey.name}_Enum_TypeB`),
                            values: enumMap,
                        }),
                    };
                    break;
                }

                default: {
                    fields[propertyName] = {
                        type: GraphQLString,
                    };
                }
            }
        });
        return fields;
    }
Example #11
Source File: generateSchema.ts    From davinci with MIT License 5 votes vote down vote up
scalarDict = {
	number: GraphQLFloat,
	string: GraphQLString,
	boolean: GraphQLBoolean,
	object: GraphQLJSON,
	date: GraphQLDateTime
}
Example #12
Source File: typeNameFromGraphQLType.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
describe('Swift code generation: Types', () => {
  let helpers: Helpers;

  beforeEach(() => {
    helpers = new Helpers({});
  });

  describe('#typeNameFromGraphQLType()', () => {
    it('should return String? for GraphQLString', () => {
      expect(helpers.typeNameFromGraphQLType(GraphQLString)).toBe('String?');
    });

    it('should return String for GraphQLNonNull(GraphQLString)', () => {
      expect(helpers.typeNameFromGraphQLType(new GraphQLNonNull(GraphQLString))).toBe('String');
    });

    it('should return [String?]? for GraphQLList(GraphQLString)', () => {
      expect(helpers.typeNameFromGraphQLType(new GraphQLList(GraphQLString))).toBe('[String?]?');
    });

    it('should return [String?] for GraphQLNonNull(GraphQLList(GraphQLString))', () => {
      expect(helpers.typeNameFromGraphQLType(new GraphQLNonNull(new GraphQLList(GraphQLString)))).toBe('[String?]');
    });

    it('should return [String]? for GraphQLList(GraphQLNonNull(GraphQLString))', () => {
      expect(helpers.typeNameFromGraphQLType(new GraphQLList(new GraphQLNonNull(GraphQLString)))).toBe('[String]?');
    });

    it('should return [String] for GraphQLNonNull(GraphQLList(GraphQLNonNull(GraphQLString)))', () => {
      expect(helpers.typeNameFromGraphQLType(new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLString))))).toBe('[String]');
    });

    it('should return [[String?]?]? for GraphQLList(GraphQLList(GraphQLString))', () => {
      expect(helpers.typeNameFromGraphQLType(new GraphQLList(new GraphQLList(GraphQLString)))).toBe('[[String?]?]?');
    });

    it('should return [[String?]]? for GraphQLList(GraphQLNonNull(GraphQLList(GraphQLString)))', () => {
      expect(helpers.typeNameFromGraphQLType(new GraphQLList(new GraphQLNonNull(new GraphQLList(GraphQLString))))).toBe('[[String?]]?');
    });

    it('should return Int? for GraphQLInt', () => {
      expect(helpers.typeNameFromGraphQLType(GraphQLInt)).toBe('Int?');
    });

    it('should return Double? for GraphQLFloat', () => {
      expect(helpers.typeNameFromGraphQLType(GraphQLFloat)).toBe('Double?');
    });

    it('should return Bool? for GraphQLBoolean', () => {
      expect(helpers.typeNameFromGraphQLType(GraphQLBoolean)).toBe('Bool?');
    });

    it('should return GraphQLID? for GraphQLID', () => {
      expect(helpers.typeNameFromGraphQLType(GraphQLID)).toBe('GraphQLID?');
    });

    it('should return String? for a custom scalar type', () => {
      expect(helpers.typeNameFromGraphQLType(new GraphQLScalarType({ name: 'CustomScalarType', serialize: String }))).toBe('String?');
    });

    it('should return a passed through custom scalar type with the passthroughCustomScalars option', () => {
      helpers.options.passthroughCustomScalars = true;
      helpers.options.customScalarsPrefix = '';

      expect(helpers.typeNameFromGraphQLType(new GraphQLScalarType({ name: 'CustomScalarType', serialize: String }))).toBe(
        'CustomScalarType?'
      );
    });

    it('should return a passed through custom scalar type with a prefix with the customScalarsPrefix option', () => {
      helpers.options.passthroughCustomScalars = true;
      helpers.options.customScalarsPrefix = 'My';

      expect(helpers.typeNameFromGraphQLType(new GraphQLScalarType({ name: 'CustomScalarType', serialize: String }))).toBe(
        'MyCustomScalarType?'
      );
    });
  });
});
Example #13
Source File: helpers.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
builtInScalarMap = {
  [GraphQLString.name]: 'String',
  [GraphQLInt.name]: 'Int',
  [GraphQLFloat.name]: 'Double',
  [GraphQLBoolean.name]: 'Bool',
  [GraphQLID.name]: 'GraphQLID',
}
Example #14
Source File: types.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
builtInScalarMap = {
  [GraphQLString.name]: 'string',
  [GraphQLInt.name]: 'number',
  [GraphQLFloat.name]: 'number',
  [GraphQLBoolean.name]: 'boolean',
  [GraphQLID.name]: 'string',
}
Example #15
Source File: helpers.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
builtInScalarMap = {
  [GraphQLString.name]: t.stringTypeAnnotation(),
  [GraphQLInt.name]: t.numberTypeAnnotation(),
  [GraphQLFloat.name]: t.numberTypeAnnotation(),
  [GraphQLBoolean.name]: t.booleanTypeAnnotation(),
  [GraphQLID.name]: t.stringTypeAnnotation(),
}
Example #16
Source File: schema_builder.ts    From graphql-mesh with MIT License 5 votes vote down vote up
/**
 * Returns the GraphQL scalar type matching the given JSON schema type
 */
function getScalarType<TSource, TContext, TArgs>({
  def,
  data,
}: CreateOrReuseSimpleTypeParams<TSource, TContext, TArgs>): GraphQLScalarType {
  switch (def.targetGraphQLType) {
    case 'id':
      def.graphQLType = GraphQLID;
      break;
    case 'string':
      def.graphQLType = GraphQLString;
      break;
    case 'integer':
      def.graphQLType = GraphQLInt;
      break;
    case 'int64':
      def.graphQLType = GraphQLBigInt;
      break;
    case 'number':
      def.graphQLType = GraphQLFloat;
      break;
    case 'float':
      def.graphQLType = GraphQLFloat;
      break;
    case 'boolean':
      def.graphQLType = GraphQLBoolean;
      break;
    case 'json':
      def.graphQLType = GraphQLJSON;
      break;
    case 'dateTime':
      def.graphQLType = GraphQLDateTime;
      break;
    default:
      throw new Error(`Cannot process schema type '${def.targetGraphQLType}'.`);
  }

  return def.graphQLType as GraphQLScalarType;
}
Example #17
Source File: custom-type-resolver.ts    From graphql-mesh with MIT License 5 votes vote down vote up
boolean = GraphQLBoolean;
Example #18
Source File: generateSchema.test.ts    From davinci with MIT License 4 votes vote down vote up
describe('schema generation', () => {
	describe('#generateGQLSchema', () => {
		it('supports primitive types', () => {
			class Customer {
				@field()
				firstname: string;
				@field()
				age: number;
				@field()
				isActive: boolean;
				@field()
				date: Date
				@field()
				blob: Object

				@field({ type: String })
				something() {}
			}

			const { schema } = generateGQLSchema({ type: Customer });

			should(schema)
				.have.property('name')
				.equal('Customer');
			const fields = schema.getFields();

			should(Object.keys(fields)).be.deepEqual(['firstname', 'age', 'isActive', 'date', 'blob', 'something']);
			should(fields.firstname.type).be.equal(GraphQLString);
			should(fields.age.type).be.equal(GraphQLFloat);
			should(fields.isActive.type).be.equal(GraphQLBoolean);
			should(fields.date.type).be.equal(GraphQLDateTime);
			should(fields.blob.type).be.equal(GraphQLJSON);
			should(fields.something.type).be.equal(GraphQLString);
		});

		it('supports nested classes', () => {
			class CustomerBirth {
				@field()
				place: string;
			}

			class Customer {
				@field()
				birth: CustomerBirth;
			}

			const { schemas } = generateGQLSchema({ type: Customer });

			should(schemas)
				.have.property('Customer')
				.instanceOf(GraphQLObjectType);

			// @ts-ignore
			const { birth } = schemas.Customer.getFields();
			should(birth.type).be.instanceOf(GraphQLObjectType);
			// @ts-ignore
			const { place } = schemas.CustomerBirth.getFields();
			should(place.type).be.equal(GraphQLString);
		});

		it('should pass fields defined as function as resolve', () => {
			class Customer {
				@field({ type: String })
				something(parent, args, context, info) {
					return { parent, args, context, info };
				}

				@field({ type: [Number] })
				somethingArray(parent, args, context, info) {
					return { parent, args, context, info };
				}
			}

			const { schema } = generateGQLSchema({ type: Customer });

			should(schema).be.instanceOf(GraphQLObjectType);

			// @ts-ignore
			const { something, somethingArray } = schema.getFields();

			should(something.type).be.equal(GraphQLString);
			should(something.resolve).be.equal(Customer.prototype.something);
			should(somethingArray.type).be.instanceOf(GraphQLList);
			should(somethingArray.type.ofType).be.equal(GraphQLFloat);
			should(somethingArray.resolve).be.equal(Customer.prototype.somethingArray);
		});

		it('should create an external resolver', () => {
			class Book {}
			class Author {
				@field()
				title: string;
			}

			// @ts-ignore
			class AuthorController {
				@fieldResolver(Book, 'authors', [Author])
				getBookAuthors() {}
			}

			const { schema } = generateGQLSchema({ type: Book });

			should(schema).be.instanceOf(GraphQLObjectType);

			// @ts-ignore
			const { authors } = schema.getFields();

			should(authors.type).be.instanceOf(GraphQLList);
			should(authors.type.ofType).be.instanceOf(GraphQLObjectType);
			should(authors.resolve).be.type('function');
		});

		it('supports arrays', () => {
			class CustomerPhone {
				@field()
				number: string;
			}

			class Customer {
				@field({ type: [CustomerPhone] })
				phones: CustomerPhone[];
				@field({ type: [String] })
				tags: string[];
			}

			const schemas: any = generateGQLSchema({ type: Customer }).schemas;

			should(Object.keys(schemas.Customer.getFields())).be.deepEqual(['phones', 'tags']);
			should(Object.keys(schemas.CustomerPhone.getFields())).be.deepEqual(['number']);

			const { tags, phones } = schemas.Customer.getFields();

			should(tags.type)
				.be.instanceOf(GraphQLList)
				.have.property('ofType')
				.equal(GraphQLString);

			should(phones.type)
				.be.instanceOf(GraphQLList)
				.have.property('ofType')
				.equal(schemas.CustomerPhone);
		});

		it('supports union types', () => {
			class Cat {
				@field()
				breed: string;
			}

			class Human {
				@field()
				phone: string;
			}

			const resolveType = () => 'Human';
			const Animal = new UnionType('Animal', [Cat, Human], resolveType);

			const schemas: any = generateGQLSchema({ type: Animal }).schemas;

			should(Object.keys(schemas)).be.deepEqual(['Cat', 'Human', 'Animal']);
			const types = schemas.Animal.getTypes();
			should(types[0].name).be.equal('Cat')
			should(types[1].name).be.equal('Human')
			should(types).have.length(2);
			should(schemas.Animal).be.instanceOf(GraphQLUnionType);
		});

		it('if transformMetadata is supplied, it should transform the metadata', () => {
			class Customer {
				@field()
				firstname: string;
			}

			const transformMetadata = (metadata, { type: t, parentType }) => {
				const type = _fp.get('opts.type', metadata) || t;

				if (type === String && parentType.name !== 'Query') {
					class Query {
						@field()
						EQ: string;
					}
					return _fp.set('opts.type', Query, metadata);
				}

				return metadata;
			};

			const schemas: any = generateGQLSchema({ type: Customer, transformMetadata }).schemas;
			const fields = schemas.Customer.getFields();
			should(fields.firstname.type).be.instanceOf(GraphQLObjectType);
			should(fields.firstname.type.getFields()).have.property('EQ');
		});
	});
});
Example #19
Source File: fieldToWhereInputSchemaMap.ts    From payload with MIT License 4 votes vote down vote up
fieldToSchemaMap: (parentName: string) => any = (parentName: string) => ({
  number: (field: NumberField) => {
    const type = GraphQLFloat;
    return {
      type: withOperators(
        field,
        type,
        parentName,
        [...operators.equality, ...operators.comparison],
      ),
    };
  },
  text: (field: TextField) => {
    const type = GraphQLString;
    return {
      type: withOperators(
        field,
        type,
        parentName,
        [...operators.equality, 'like', 'contains'],
      ),
    };
  },
  email: (field: EmailField) => {
    const type = EmailAddressResolver;
    return {
      type: withOperators(
        field,
        type,
        parentName,
        [...operators.equality, 'like', 'contains'],
      ),
    };
  },
  textarea: (field: TextareaField) => {
    const type = GraphQLString;
    return {
      type: withOperators(
        field,
        type,
        parentName,
        [...operators.equality, 'like', 'contains'],
      ),
    };
  },
  richText: (field: RichTextField) => {
    const type = GraphQLJSON;
    return {
      type: withOperators(
        field,
        type,
        parentName,
        [...operators.equality, 'like', 'contains'],
      ),
    };
  },
  code: (field: CodeField) => {
    const type = GraphQLString;
    return {
      type: withOperators(
        field,
        type,
        parentName,
        [...operators.equality, 'like', 'contains'],
      ),
    };
  },
  radio: (field: RadioField) => ({
    type: withOperators(
      field,
      new GraphQLEnumType({
        name: `${combineParentName(parentName, field.name)}_Input`,
        values: field.options.reduce((values, option) => {
          if (optionIsObject(option)) {
            return {
              ...values,
              [formatName(option.value)]: {
                value: option.value,
              },
            };
          }

          return {
            ...values,
            [formatName(option)]: {
              value: option,
            },
          };
        }, {}),
      }),
      parentName,
      [...operators.equality, 'like', 'contains'],
    ),
  }),
  date: (field: DateField) => {
    const type = DateTimeResolver;
    return {
      type: withOperators(
        field,
        type,
        parentName,
        [...operators.equality, ...operators.comparison, 'like'],
      ),
    };
  },
  point: (field: PointField) => {
    const type = GraphQLList(GraphQLFloat);
    return {
      type: withOperators(
        field,
        type,
        parentName,
        [...operators.equality, ...operators.comparison, ...operators.geo],
      ),
    };
  },
  relationship: (field: RelationshipField) => {
    let type = withOperators(
      field,
      GraphQLString,
      parentName,
      [...operators.equality, ...operators.contains],
    );

    if (Array.isArray(field.relationTo)) {
      type = new GraphQLInputObjectType({
        name: `${combineParentName(parentName, field.name)}_Relation`,
        fields: {
          relationTo: {
            type: new GraphQLEnumType({
              name: `${combineParentName(parentName, field.name)}_Relation_RelationTo`,
              values: field.relationTo.reduce((values, relation) => ({
                ...values,
                [formatName(relation)]: {
                  value: relation,
                },
              }), {}),
            }),
          },
          value: { type: GraphQLString },
        },
      });
    }

    return { type };
  },
  upload: (field: UploadField) => ({
    type: withOperators(
      field,
      GraphQLString,
      parentName,
      [...operators.equality],
    ),
  }),
  checkbox: (field: CheckboxField) => ({
    type: withOperators(
      field,
      GraphQLBoolean,
      parentName,
      [...operators.equality],
    ),
  }),
  select: (field: SelectField) => ({
    type: withOperators(
      field,
      new GraphQLEnumType({
        name: `${combineParentName(parentName, field.name)}_Input`,
        values: field.options.reduce((values, option) => {
          if (typeof option === 'object' && option.value) {
            return {
              ...values,
              [formatName(option.value)]: {
                value: option.value,
              },
            };
          }

          if (typeof option === 'string') {
            return {
              ...values,
              [option]: {
                value: option,
              },
            };
          }

          return values;
        }, {}),
      }),
      parentName,
      [...operators.equality, ...operators.contains],
    ),
  }),
  array: (field: ArrayField) => recursivelyBuildNestedPaths(parentName, field),
  group: (field: GroupField) => recursivelyBuildNestedPaths(parentName, field),
  row: (field: RowField) => field.fields.reduce((rowSchema, rowField) => {
    const getFieldSchema = fieldToSchemaMap(parentName)[rowField.type];

    if (getFieldSchema) {
      const rowFieldSchema = getFieldSchema(rowField);

      if (fieldHasSubFields(rowField)) {
        return [
          ...rowSchema,
          ...rowFieldSchema,
        ];
      }

      if (fieldAffectsData(rowField)) {
        return [
          ...rowSchema,
          {
            key: rowField.name,
            type: rowFieldSchema,
          },
        ];
      }
    }


    return rowSchema;
  }, []),
})
Example #20
Source File: queryHelpers.tests.ts    From davinci with MIT License 4 votes vote down vote up
describe('mongooseHelpers', () => {
	describe('#toMongodbQuery', () => {
		it('should correctly transform the query', () => {
			const query = {
				firstname: { EQ: 'Mike' },
				lastname: { NOT: { EQ: 'Jordan' } },
				home: { address: { NE: 'Foobar' } },
				myNumber: { GTE: 1, LTE: 10 },
				OR: [{ lastname: { IN: ['Foo'] } }, { lastname: { NIN: ['Bar'] } }],
				AND: [{ home: { address: { EXISTS: true } } }, { OR: [{ weight: { EQ: 122 } }, { age: { EQ: 30 } }] }]
			};

			const mongodbQuery = toMongodbQuery(query);

			should(mongodbQuery).be.deepEqual({
				firstname: {
					$eq: 'Mike'
				},
				lastname: {
					$not: {
						$eq: 'Jordan'
					}
				},
				'home.address': {
					$ne: 'Foobar'
				},
				myNumber: {
					$gte: 1,
					$lte: 10
				},
				$or: [
					{
						lastname: {
							$in: ['Foo']
						}
					},
					{
						lastname: {
							$nin: ['Bar']
						}
					}
				],
				$and: [
					{
						'home.address': {
							$exists: true
						}
					},
					{
						$or: [
							{
								weight: {
									$eq: 122
								}
							},
							{
								age: {
									$eq: 30
								}
							}
						]
					}
				]
			});
		});
	});

	describe('#withOperators', () => {
		it('should create a class with added operators', () => {
			class Phone {
				@field()
				isPrimary: boolean;

				@field()
				number: number;
			}

			class MyClass {
				@field()
				firstname: string;

				@field()
				phone: Phone;
			}

			const MyClassFilter = withOperators(MyClass);

			const { schema: gqlSchema } = generateGQLSchema({ type: MyClassFilter });
			const fields = gqlSchema.getFields();

			const firstnameFields = fields.firstname.type.getFields();
			const phoneFields = fields.phone.type.getFields();

			should(Object.keys(fields)).be.deepEqual(['firstname', 'phone', 'AND', 'OR', 'NOR']);
			should(Object.keys(firstnameFields)).be.deepEqual([
				'EQ',
				'NE',
				'GT',
				'GTE',
				'LT',
				'LTE',
				'IN',
				'NIN',
				'EXISTS',
				'NOT'
			]);
			should(firstnameFields.EQ.type).be.equal(GraphQLString);
			should(fields.phone.type).be.instanceOf(GraphQLObjectType);
			should(Object.keys(phoneFields)).be.deepEqual(['isPrimary', 'number', 'AND', 'OR', 'NOR']);
			should(Object.keys(phoneFields.number.type.getFields())).be.deepEqual([
				'EQ',
				'NE',
				'GT',
				'GTE',
				'LT',
				'LTE',
				'IN',
				'NIN',
				'EXISTS',
				'NOT'
			]);
			should(phoneFields.number.type.getFields().EQ.type).be.equal(GraphQLFloat);
			should(phoneFields.isPrimary.type.getFields().EQ.type).be.equal(GraphQLBoolean);
		});
	});
});
Example #21
Source File: index.ts    From graphql-mesh with MIT License 4 votes vote down vote up
async getMeshSource() {
    const { schemaHeaders, serviceName, operationHeaders } = this.config;

    const thriftAST = await this.idl.getWithSet(async () => {
      const rawThrift = await readFileOrUrl<string>(this.config.idl, {
        allowUnknownExtensions: true,
        cwd: this.baseDir,
        headers: schemaHeaders,
      });
      const parseResult = parse(rawThrift, { organize: false });
      if (parseResult.type === SyntaxType.ThriftErrors) {
        throw new AggregateError(parseResult.errors);
      }
      return parseResult;
    });

    const enumTypeMap = new Map<string, GraphQLEnumType>();
    const outputTypeMap = new Map<string, GraphQLOutputType>();
    const inputTypeMap = new Map<string, GraphQLInputType>();
    const rootFields: GraphQLFieldConfigMap<any, any> = {};
    const annotations: IThriftAnnotations = {};
    const methodAnnotations: IMethodAnnotations = {};
    const methodNames: string[] = [];
    const methodParameters: {
      [methodName: string]: number;
    } = {};

    type TypeVal = BaseTypeVal | ListTypeVal | SetTypeVal | MapTypeVal | EnumTypeVal | StructTypeVal | VoidTypeVal;
    type BaseTypeVal = {
      id?: number;
      type: TType.BOOL | TType.BYTE | TType.DOUBLE | TType.I16 | TType.I32 | TType.I64 | TType.STRING;
    };
    type ListTypeVal = { id?: number; type: TType.LIST; elementType: TypeVal };
    type SetTypeVal = { id?: number; type: TType.SET; elementType: TypeVal };
    type MapTypeVal = { id?: number; type: TType.MAP; keyType: TypeVal; valType: TypeVal };
    type EnumTypeVal = { id?: number; type: TType.ENUM };
    type StructTypeVal = { id?: number; type: TType.STRUCT; name: string; fields: TypeMap };
    type VoidTypeVal = { id?: number; type: TType.VOID };
    type TypeMap = Record<string, TypeVal>;
    const topTypeMap: TypeMap = {};

    class MeshThriftClient<Context = any> extends ThriftClient<Context> {
      public static readonly serviceName: string = serviceName;
      public static readonly annotations: IThriftAnnotations = annotations;
      public static readonly methodAnnotations: IMethodAnnotations = methodAnnotations;
      public static readonly methodNames: Array<string> = methodNames;
      public readonly _serviceName: string = serviceName;
      public readonly _annotations: IThriftAnnotations = annotations;
      public readonly _methodAnnotations: IMethodAnnotations = methodAnnotations;
      public readonly _methodNames: Array<string> = methodNames;
      public readonly _methodParameters?: {
        [methodName: string]: number;
      } = methodParameters;

      writeType(typeVal: TypeVal, value: any, output: TProtocol) {
        switch (typeVal.type) {
          case TType.BOOL:
            output.writeBool(value);
            break;
          case TType.BYTE:
            output.writeByte(value);
            break;
          case TType.DOUBLE:
            output.writeDouble(value);
            break;
          case TType.I16:
            output.writeI16(value);
            break;
          case TType.I32:
            output.writeI32(value);
            break;
          case TType.I64:
            output.writeI64(value.toString());
            break;
          case TType.STRING:
            output.writeString(value);
            break;
          case TType.STRUCT: {
            output.writeStructBegin(typeVal.name);
            const typeMap = typeVal.fields;
            for (const argName in value) {
              const argType = typeMap[argName];
              const argVal = value[argName];
              if (argType) {
                output.writeFieldBegin(argName, argType.type, argType.id);
                this.writeType(argType, argVal, output);
                output.writeFieldEnd();
              }
            }
            output.writeFieldStop();
            output.writeStructEnd();
            break;
          }
          case TType.ENUM:
            // TODO: A
            break;
          case TType.MAP: {
            const keys = Object.keys(value);
            output.writeMapBegin(typeVal.keyType.type, typeVal.valType.type, keys.length);
            for (const key of keys) {
              this.writeType(typeVal.keyType, key, output);
              const val = value[key];
              this.writeType(typeVal.valType, val, output);
            }
            output.writeMapEnd();
            break;
          }
          case TType.LIST:
            output.writeListBegin(typeVal.elementType.type, value.length);
            for (const element of value) {
              this.writeType(typeVal.elementType, element, output);
            }
            output.writeListEnd();
            break;
          case TType.SET:
            output.writeSetBegin(typeVal.elementType.type, value.length);
            for (const element of value) {
              this.writeType(typeVal.elementType, element, output);
            }
            output.writeSetEnd();
            break;
        }
      }

      readType(type: TType, input: TProtocol): any {
        switch (type) {
          case TType.BOOL:
            return input.readBool();
          case TType.BYTE:
            return input.readByte();
          case TType.DOUBLE:
            return input.readDouble();
          case TType.I16:
            return input.readI16();
          case TType.I32:
            return input.readI32();
          case TType.I64:
            return BigInt(input.readI64().toString());
          case TType.STRING:
            return input.readString();
          case TType.STRUCT: {
            const result: any = {};
            input.readStructBegin();
            while (true) {
              const field: IThriftField = input.readFieldBegin();
              const fieldType = field.fieldType;
              const fieldName = field.fieldName || 'success';
              if (fieldType === TType.STOP) {
                break;
              }
              result[fieldName] = this.readType(fieldType, input);
              input.readFieldEnd();
            }
            input.readStructEnd();
            return result;
          }
          case TType.ENUM:
            // TODO: A
            break;
          case TType.MAP: {
            const result: any = {};
            const map = input.readMapBegin();
            for (let i = 0; i < map.size; i++) {
              const key = this.readType(map.keyType, input);
              const value = this.readType(map.valueType, input);
              result[key] = value;
            }
            input.readMapEnd();
            return result;
          }
          case TType.LIST: {
            const result: any[] = [];
            const list = input.readListBegin();
            for (let i = 0; i < list.size; i++) {
              const element = this.readType(list.elementType, input);
              result.push(element);
            }
            input.readListEnd();
            return result;
          }
          case TType.SET: {
            const result: any[] = [];
            const list = input.readSetBegin();
            for (let i = 0; i < list.size; i++) {
              const element = this.readType(list.elementType, input);
              result.push(element);
            }
            input.readSetEnd();
            return result;
          }
        }
      }

      async doRequest(methodName: string, args: any, fields: TypeMap, context?: any) {
        const Transport = this.transport;
        const Protocol = this.protocol;
        const writer: TTransport = new Transport();
        const output: TProtocol = new Protocol(writer);
        const id = this.incrementRequestId();
        output.writeMessageBegin(methodName, MessageType.CALL, id);
        this.writeType(
          {
            name: pascalCase(methodName) + '__Args',
            type: TType.STRUCT,
            fields,
            id,
          },
          args,
          output
        );
        output.writeMessageEnd();
        const data: Buffer = await this.connection.send(writer.flush(), context);
        const reader: TTransport = this.transport.receiver(data);
        const input: TProtocol = new Protocol(reader);
        const { fieldName, messageType }: IThriftMessage = input.readMessageBegin();
        if (fieldName === methodName) {
          if (messageType === MessageType.EXCEPTION) {
            const err: TApplicationException = TApplicationExceptionCodec.decode(input);
            input.readMessageEnd();
            return Promise.reject(err);
          } else {
            const result = this.readType(TType.STRUCT, input);
            input.readMessageEnd();
            if (result.success != null) {
              return result.success;
            } else {
              throw new TApplicationException(
                TApplicationExceptionType.UNKNOWN,
                methodName + ' failed: unknown result'
              );
            }
          }
        } else {
          throw new TApplicationException(
            TApplicationExceptionType.WRONG_METHOD_NAME,
            'Received a response to an unknown RPC function: ' + fieldName
          );
        }
      }
    }
    const thriftHttpClient = createHttpClient(MeshThriftClient, {
      ...this.config,
      requestOptions: {
        headers: operationHeaders,
      },
    });

    function processComments(comments: Comment[]) {
      return comments.map(comment => comment.value).join('\n');
    }

    function getGraphQLFunctionType(
      functionType: FunctionType,
      id = Math.random()
    ): { outputType: GraphQLOutputType; inputType: GraphQLInputType; typeVal: TypeVal } {
      let inputType: GraphQLInputType;
      let outputType: GraphQLOutputType;
      let typeVal: TypeVal;
      switch (functionType.type) {
        case SyntaxType.BinaryKeyword:
        case SyntaxType.StringKeyword:
          inputType = GraphQLString;
          outputType = GraphQLString;
          break;
        case SyntaxType.DoubleKeyword:
          inputType = GraphQLFloat;
          outputType = GraphQLFloat;
          typeVal = typeVal! || { type: TType.DOUBLE };
          break;
        case SyntaxType.VoidKeyword:
          typeVal = typeVal! || { type: TType.VOID };
          inputType = GraphQLVoid;
          outputType = GraphQLVoid;
          break;
        case SyntaxType.BoolKeyword:
          typeVal = typeVal! || { type: TType.BOOL };
          inputType = GraphQLBoolean;
          outputType = GraphQLBoolean;
          break;
        case SyntaxType.I8Keyword:
          inputType = GraphQLInt;
          outputType = GraphQLInt;
          typeVal = typeVal! || { type: TType.I08 };
          break;
        case SyntaxType.I16Keyword:
          inputType = GraphQLInt;
          outputType = GraphQLInt;
          typeVal = typeVal! || { type: TType.I16 };
          break;
        case SyntaxType.I32Keyword:
          inputType = GraphQLInt;
          outputType = GraphQLInt;
          typeVal = typeVal! || { type: TType.I32 };
          break;
        case SyntaxType.ByteKeyword:
          inputType = GraphQLByte;
          outputType = GraphQLByte;
          typeVal = typeVal! || { type: TType.BYTE };
          break;
        case SyntaxType.I64Keyword:
          inputType = GraphQLBigInt;
          outputType = GraphQLBigInt;
          typeVal = typeVal! || { type: TType.I64 };
          break;
        case SyntaxType.ListType: {
          const ofTypeList = getGraphQLFunctionType(functionType.valueType, id);
          inputType = new GraphQLList(ofTypeList.inputType);
          outputType = new GraphQLList(ofTypeList.outputType);
          typeVal = typeVal! || { type: TType.LIST, elementType: ofTypeList.typeVal };
          break;
        }
        case SyntaxType.SetType: {
          const ofSetType = getGraphQLFunctionType(functionType.valueType, id);
          inputType = new GraphQLList(ofSetType.inputType);
          outputType = new GraphQLList(ofSetType.outputType);
          typeVal = typeVal! || { type: TType.SET, elementType: ofSetType.typeVal };
          break;
        }
        case SyntaxType.MapType: {
          inputType = GraphQLJSON;
          outputType = GraphQLJSON;
          const ofTypeKey = getGraphQLFunctionType(functionType.keyType, id);
          const ofTypeValue = getGraphQLFunctionType(functionType.valueType, id);
          typeVal = typeVal! || { type: TType.MAP, keyType: ofTypeKey.typeVal, valType: ofTypeValue.typeVal };
          break;
        }
        case SyntaxType.Identifier: {
          const typeName = functionType.value;
          if (enumTypeMap.has(typeName)) {
            const enumType = enumTypeMap.get(typeName)!;
            inputType = enumType;
            outputType = enumType;
          }
          if (inputTypeMap.has(typeName)) {
            inputType = inputTypeMap.get(typeName)!;
          }
          if (outputTypeMap.has(typeName)) {
            outputType = outputTypeMap.get(typeName)!;
          }
          typeVal = topTypeMap[typeName];
          break;
        }
        default:
          throw new Error(`Unknown function type: ${util.inspect(functionType)}!`);
      }
      return {
        inputType: inputType!,
        outputType: outputType!,
        typeVal: {
          ...typeVal!,
          id,
        },
      };
    }

    const { args: commonArgs, contextVariables } = parseInterpolationStrings(Object.values(operationHeaders || {}));

    const headersFactory = getInterpolatedHeadersFactory(operationHeaders);

    for (const statement of thriftAST.body) {
      switch (statement.type) {
        case SyntaxType.EnumDefinition:
          enumTypeMap.set(
            statement.name.value,
            new GraphQLEnumType({
              name: statement.name.value,
              description: processComments(statement.comments),
              values: statement.members.reduce(
                (prev, curr) => ({
                  ...prev,
                  [curr.name.value]: {
                    description: processComments(curr.comments),
                    value: curr.name.value,
                  },
                }),
                {} as GraphQLEnumValueConfigMap
              ),
            })
          );
          break;
        case SyntaxType.StructDefinition: {
          const structName = statement.name.value;
          const description = processComments(statement.comments);
          const objectFields: GraphQLFieldConfigMap<any, any> = {};
          const inputObjectFields: GraphQLInputFieldConfigMap = {};
          const structTypeVal: StructTypeVal = {
            id: Math.random(),
            name: structName,
            type: TType.STRUCT,
            fields: {},
          };
          topTypeMap[structName] = structTypeVal;
          const structFieldTypeMap = structTypeVal.fields;
          for (const field of statement.fields) {
            const fieldName = field.name.value;
            let fieldOutputType: GraphQLOutputType;
            let fieldInputType: GraphQLInputType;
            const description = processComments(field.comments);
            const processedFieldTypes = getGraphQLFunctionType(field.fieldType, field.fieldID?.value);
            fieldOutputType = processedFieldTypes.outputType;
            fieldInputType = processedFieldTypes.inputType;

            if (field.requiredness === 'required') {
              fieldOutputType = new GraphQLNonNull(fieldOutputType);
              fieldInputType = new GraphQLNonNull(fieldInputType);
            }

            objectFields[fieldName] = {
              type: fieldOutputType,
              description,
            };
            inputObjectFields[fieldName] = {
              type: fieldInputType,
              description,
            };
            structFieldTypeMap[fieldName] = processedFieldTypes.typeVal;
          }
          outputTypeMap.set(
            structName,
            new GraphQLObjectType({
              name: structName,
              description,
              fields: objectFields,
            })
          );
          inputTypeMap.set(
            structName,
            new GraphQLInputObjectType({
              name: structName + 'Input',
              description,
              fields: inputObjectFields,
            })
          );
          break;
        }
        case SyntaxType.ServiceDefinition:
          for (const fnIndex in statement.functions) {
            const fn = statement.functions[fnIndex];
            const fnName = fn.name.value;
            const description = processComments(fn.comments);
            const { outputType: returnType } = getGraphQLFunctionType(fn.returnType, Number(fnIndex) + 1);
            const args: GraphQLFieldConfigArgumentMap = {};
            for (const argName in commonArgs) {
              const typeNameOrType = commonArgs[argName].type;
              args[argName] = {
                type:
                  typeof typeNameOrType === 'string' ? inputTypeMap.get(typeNameOrType) : typeNameOrType || GraphQLID,
              };
            }
            const fieldTypeMap: TypeMap = {};
            for (const field of fn.fields) {
              const fieldName = field.name.value;
              const fieldDescription = processComments(field.comments);
              let { inputType: fieldType, typeVal } = getGraphQLFunctionType(field.fieldType, field.fieldID?.value);
              if (field.requiredness === 'required') {
                fieldType = new GraphQLNonNull(fieldType);
              }
              args[fieldName] = {
                type: fieldType,
                description: fieldDescription,
              };
              fieldTypeMap[fieldName] = typeVal;
            }
            rootFields[fnName] = {
              type: returnType,
              description,
              args,
              resolve: async (root, args, context, info) =>
                thriftHttpClient.doRequest(fnName, args, fieldTypeMap, {
                  headers: headersFactory({ root, args, context, info, env: process.env }),
                }),
            };
            methodNames.push(fnName);
            methodAnnotations[fnName] = { annotations: {}, fieldAnnotations: {} };
            methodParameters[fnName] = fn.fields.length + 1;
          }
          break;
        case SyntaxType.TypedefDefinition: {
          const { inputType, outputType } = getGraphQLFunctionType(statement.definitionType, Math.random());
          const typeName = statement.name.value;
          inputTypeMap.set(typeName, inputType);
          outputTypeMap.set(typeName, outputType);
          break;
        }
      }
    }

    const queryObjectType = new GraphQLObjectType({
      name: 'Query',
      fields: rootFields,
    });

    const schema = new GraphQLSchema({
      query: queryObjectType,
    });

    return {
      schema,
      contextVariables,
    };
  }
Example #22
Source File: buildObjectType.ts    From payload with MIT License 4 votes vote down vote up
function buildObjectType(name: string, fields: Field[], parentName: string, baseFields: BaseFields = {}): GraphQLType {
  const recursiveBuildObjectType = buildObjectType.bind(this);

  const fieldToSchemaMap = {
    number: (field: NumberField) => ({ type: withNullableType(field, GraphQLFloat) }),
    text: (field: TextField) => ({ type: withNullableType(field, GraphQLString) }),
    email: (field: EmailField) => ({ type: withNullableType(field, EmailAddressResolver) }),
    textarea: (field: TextareaField) => ({ type: withNullableType(field, GraphQLString) }),
    code: (field: CodeField) => ({ type: withNullableType(field, GraphQLString) }),
    date: (field: DateField) => ({ type: withNullableType(field, DateTimeResolver) }),
    point: (field: PointField) => ({ type: withNullableType(field, new GraphQLList(GraphQLFloat)) }),
    richText: (field: RichTextField) => ({
      type: withNullableType(field, GraphQLJSON),
      async resolve(parent, args, context) {
        if (args.depth > 0) {
          await createRichTextRelationshipPromise({
            req: context.req,
            siblingDoc: parent,
            depth: args.depth,
            field,
            showHiddenFields: false,
          });
        }

        return parent[field.name];
      },
      args: {
        depth: {
          type: GraphQLInt,
        },
      },
    }),
    upload: (field: UploadField) => {
      const { relationTo, label } = field;

      const uploadName = combineParentName(parentName, label === false ? toWords(field.name, true) : label);

      // If the relationshipType is undefined at this point,
      // it can be assumed that this blockType can have a relationship
      // to itself. Therefore, we set the relationshipType equal to the blockType
      // that is currently being created.

      const type = this.collections[relationTo].graphQL.type || newlyCreatedBlockType;

      const uploadArgs = {} as LocaleInputType;

      if (this.config.localization) {
        uploadArgs.locale = {
          type: this.types.localeInputType,
        };

        uploadArgs.fallbackLocale = {
          type: this.types.fallbackLocaleInputType,
        };
      }

      const relatedCollectionSlug = field.relationTo;
      const relatedCollection = this.collections[relatedCollectionSlug];

      const { find } = this.operations.collections;

      const upload = {
        args: uploadArgs,
        type,
        extensions: { complexity: 20 },
        async resolve(parent, args, context) {
          const value = parent[field.name];
          const locale = args.locale || context.req.locale;
          const fallbackLocale = args.fallbackLocale || context.req.fallbackLocale;

          let id = value;

          if (id) {
            id = id.toString();

            const relatedDocumentQuery = {
              collection: relatedCollection,
              where: {
                ...(args.where || {}),
                _id: {
                  equals: id,
                },
              },
              res: context.res,
              req: {
                ...context.req,
                locale,
                fallbackLocale,
              },
              depth: 0,
              pagination: false,
            };

            const relatedDocument = await find(relatedDocumentQuery);

            if (relatedDocument.docs[0]) return relatedDocument.docs[0];

            return null;
          }

          return null;
        },
      };

      const whereFields = this.collections[relationTo].config.fields;

      upload.args.where = {
        type: this.buildWhereInputType(
          uploadName,
          whereFields,
          uploadName,
        ),
      };

      return upload;
    },
    radio: (field: RadioField) => ({
      type: withNullableType(
        field,
        new GraphQLEnumType({
          name: combineParentName(parentName, field.name),
          values: formatOptions(field),
        }),
      ),
    }),
    checkbox: (field: CheckboxField) => ({ type: withNullableType(field, GraphQLBoolean) }),
    select: (field: SelectField) => {
      const fullName = combineParentName(parentName, field.name);

      let type: GraphQLType = new GraphQLEnumType({
        name: fullName,
        values: formatOptions(field),
      });

      type = field.hasMany ? new GraphQLList(type) : type;
      type = withNullableType(field, type);

      return { type };
    },
    relationship: (field: RelationshipField) => {
      const { relationTo, label } = field;
      const isRelatedToManyCollections = Array.isArray(relationTo);
      const hasManyValues = field.hasMany;
      const relationshipName = combineParentName(parentName, label === false ? toWords(field.name, true) : label);

      let type;
      let relationToType = null;

      if (Array.isArray(relationTo)) {
        relationToType = new GraphQLEnumType({
          name: `${relationshipName}_RelationTo`,
          values: relationTo.reduce((relations, relation) => ({
            ...relations,
            [formatName(relation)]: {
              value: relation,
            },
          }), {}),
        });

        const types = relationTo.map((relation) => this.collections[relation].graphQL.type);

        type = new GraphQLObjectType({
          name: `${relationshipName}_Relationship`,
          fields: {
            relationTo: {
              type: relationToType,
            },
            value: {
              type: new GraphQLUnionType({
                name: relationshipName,
                types,
                async resolveType(data, { req: { payload } }) {
                  return payload.collections[data.collection].graphQL.type.name;
                },
              }),
            },
          },
        });
      } else {
        ({ type } = this.collections[relationTo as string].graphQL);
      }

      // If the relationshipType is undefined at this point,
      // it can be assumed that this blockType can have a relationship
      // to itself. Therefore, we set the relationshipType equal to the blockType
      // that is currently being created.

      type = type || newlyCreatedBlockType;

      const relationshipArgs: {
        locale?: unknown
        fallbackLocale?: unknown
        where?: unknown
        page?: unknown
        limit?: unknown
      } = {};

      if (this.config.localization) {
        relationshipArgs.locale = {
          type: this.types.localeInputType,
        };

        relationshipArgs.fallbackLocale = {
          type: this.types.fallbackLocaleInputType,
        };
      }

      const {
        collections,
        operations: {
          collections: {
            find,
          },
        },
      } = this;

      const relationship = {
        args: relationshipArgs,
        type: hasManyValues ? new GraphQLList(type) : type,
        extensions: { complexity: 10 },
        async resolve(parent, args, context) {
          const value = parent[field.name];
          const locale = args.locale || context.req.locale;
          const fallbackLocale = args.fallbackLocale || context.req.fallbackLocale;
          let relatedCollectionSlug = field.relationTo;

          if (hasManyValues) {
            const results = [];
            const resultPromises = [];

            const createPopulationPromise = async (relatedDoc, i) => {
              let id = relatedDoc;
              let collectionSlug = field.relationTo;

              if (isRelatedToManyCollections) {
                collectionSlug = relatedDoc.relationTo;
                id = relatedDoc.value;
              }

              const result = await find({
                collection: collections[collectionSlug as string],
                where: {
                  ...(args.where || {}),
                  _id: {
                    equals: id,
                  },
                },
                res: context.res,
                req: {
                  ...context.req,
                  locale,
                  fallbackLocale,
                },
                depth: 0,
                pagination: false,
              });

              if (result.docs.length === 1) {
                if (isRelatedToManyCollections) {
                  results[i] = {
                    relationTo: collectionSlug,
                    value: {
                      ...result.docs[0],
                      collection: collectionSlug,
                    },
                  };
                } else {
                  [results[i]] = result.docs;
                }
              }
            };

            if (value) {
              value.forEach((relatedDoc, i) => {
                resultPromises.push(createPopulationPromise(relatedDoc, i));
              });
            }

            await Promise.all(resultPromises);
            return results;
          }

          let id = value;
          if (isRelatedToManyCollections && value) {
            id = value.value;
            relatedCollectionSlug = value.relationTo;
          }

          if (id) {
            id = id.toString();

            const relatedDocumentQuery = {
              collection: collections[relatedCollectionSlug as string],
              where: {
                ...(args.where || {}),
                id: {
                  equals: id,
                },
              },
              ...context,
              depth: 0,
            };

            if (args.page) relatedDocumentQuery.paginate.page = args.page;
            if (args.limit) relatedDocumentQuery.paginate.limit = args.limit;

            const relatedDocument = await find(relatedDocumentQuery);

            if (relatedDocument.docs[0]) {
              if (isRelatedToManyCollections) {
                return {
                  relationTo: relatedCollectionSlug,
                  value: {
                    ...relatedDocument.docs[0],
                    collection: relatedCollectionSlug,
                  },
                };
              }

              return relatedDocument.docs[0];
            }

            return null;
          }

          return null;
        },
      };

      if (hasManyValues) {
        relationship.args.page = { type: GraphQLInt };
        relationship.args.limit = { type: GraphQLInt };
      }

      if (Array.isArray(relationTo)) {
        const relatedCollectionFields = relationTo.reduce((allFields, relation) => [
          ...allFields,
          ...collections[relation].config.fields,
        ], []);

        relationship.args.where = {
          type: this.buildWhereInputType(
            relationshipName,
            relatedCollectionFields,
            relationshipName,
          ),
        };
      } else {
        const whereFields = this.collections[relationTo].config.fields;

        relationship.args.where = {
          type: this.buildWhereInputType(
            relationshipName,
            whereFields,
            relationshipName,
          ),
        };
      }

      return relationship;
    },
    array: (field: ArrayField) => {
      const fullName = combineParentName(parentName, field.label === false ? toWords(field.name, true) : field.label);
      let type = recursiveBuildObjectType(fullName, field.fields, fullName);
      type = new GraphQLList(withNullableType(field, type));

      return { type };
    },
    group: (field: GroupField) => {
      const fullName = combineParentName(parentName, field.label === false ? toWords(field.name, true) : field.label);
      const type = recursiveBuildObjectType(fullName, field.fields, fullName);

      return { type };
    },
    blocks: (field: BlockField) => {
      const blockTypes = field.blocks.map((block) => {
        this.buildBlockType(block);
        return this.types.blockTypes[block.slug];
      });

      const fullName = combineParentName(parentName, field.label === false ? toWords(field.name, true) : field.label);

      const type = new GraphQLList(new GraphQLUnionType({
        name: fullName,
        types: blockTypes,
        resolveType: (data) => this.types.blockTypes[data.blockType].name,
      }));

      return { type };
    },
    row: (field) => field.fields.reduce((subFieldSchema, subField) => {
      const buildSchemaType = fieldToSchemaMap[subField.type];

      if (!fieldIsPresentationalOnly(subField) && buildSchemaType) {
        return {
          ...subFieldSchema,
          [formatName(subField.name)]: buildSchemaType(subField),
        };
      }

      return subFieldSchema;
    }, {}),
  };

  const objectSchema = {
    name,
    fields: () => fields.reduce((schema, field) => {
      if (!fieldIsPresentationalOnly(field) && !field.hidden) {
        const fieldSchema = fieldToSchemaMap[field.type];
        if (fieldSchema) {
          if (fieldAffectsData(field)) {
            return {
              ...schema,
              [formatName(field.name)]: fieldSchema(field),
            };
          }

          return {
            ...schema,
            ...fieldSchema(field),
          };
        }
      }

      return schema;
    }, baseFields),
  };

  const newlyCreatedBlockType = new GraphQLObjectType(objectSchema);

  return newlyCreatedBlockType;
}
Example #23
Source File: buildMutationInputType.ts    From payload with MIT License 4 votes vote down vote up
function buildMutationInputType(name: string, fields: Field[], parentName: string, forceNullable = false): GraphQLInputObjectType {
  const fieldToSchemaMap = {
    number: (field: NumberField) => {
      const type = field.name === 'id' ? GraphQLInt : GraphQLFloat;
      return { type: withNullableType(field, type, forceNullable) };
    },
    text: (field: TextField) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
    email: (field: EmailField) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
    textarea: (field: TextareaField) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
    richText: (field: RichTextField) => ({ type: withNullableType(field, GraphQLJSON, forceNullable) }),
    code: (field: CodeField) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
    date: (field: DateField) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
    upload: (field: UploadField) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
    radio: (field: RadioField) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
    point: (field: PointField) => ({ type: withNullableType(field, GraphQLList(GraphQLFloat), forceNullable) }),
    checkbox: () => ({ type: GraphQLBoolean }),
    select: (field: SelectField) => {
      const formattedName = `${combineParentName(parentName, field.name)}_MutationInput`;
      let type: GraphQLType = new GraphQLEnumType({
        name: formattedName,
        values: field.options.reduce((values, option) => {
          if (typeof option === 'object' && option.value) {
            return {
              ...values,
              [formatName(option.value)]: {
                value: option.value,
              },
            };
          }

          if (typeof option === 'string') {
            return {
              ...values,
              [option]: {
                value: option,
              },
            };
          }

          return values;
        }, {}),
      });

      type = field.hasMany ? new GraphQLList(type) : type;
      type = withNullableType(field, type, forceNullable);

      return { type };
    },
    relationship: (field: RelationshipField) => {
      const { relationTo } = field;
      type PayloadGraphQLRelationshipType = GraphQLScalarType | GraphQLList<GraphQLScalarType> | GraphQLInputObjectType;
      let type: PayloadGraphQLRelationshipType;

      if (Array.isArray(relationTo)) {
        const fullName = `${combineParentName(parentName, field.label === false ? toWords(field.name, true) : field.label)}RelationshipInput`;
        type = new GraphQLInputObjectType({
          name: fullName,
          fields: {
            relationTo: {
              type: new GraphQLEnumType({
                name: `${fullName}RelationTo`,
                values: relationTo.reduce((values, option) => ({
                  ...values,
                  [formatName(option)]: {
                    value: option,
                  },
                }), {}),
              }),
            },
            value: { type: GraphQLJSON },
          },
        });
      } else {
        type = getCollectionIDType(payload.collections[relationTo].config);
      }

      return { type: field.hasMany ? new GraphQLList(type) : type };
    },
    array: (field: ArrayField) => {
      const fullName = combineParentName(parentName, field.label === false ? toWords(field.name, true) : field.label);
      let type: GraphQLType | GraphQLList<GraphQLType> = buildMutationInputType(fullName, field.fields, fullName);
      type = new GraphQLList(withNullableType(field, type, forceNullable));
      return { type };
    },
    group: (field: GroupField) => {
      const requiresAtLeastOneField = field.fields.some((subField) => (!fieldIsPresentationalOnly(subField) && subField.required && !subField.localized));
      const fullName = combineParentName(parentName, field.label === false ? toWords(field.name, true) : field.label);
      let type: GraphQLType = buildMutationInputType(fullName, field.fields, fullName);
      if (requiresAtLeastOneField) type = new GraphQLNonNull(type);
      return { type };
    },
    blocks: () => ({ type: GraphQLJSON }),
    row: (field: RowField) => field.fields.reduce((acc, rowField: RowField) => {
      const getFieldSchema = fieldToSchemaMap[rowField.type];

      if (getFieldSchema) {
        const fieldSchema = getFieldSchema(rowField);

        return [
          ...acc,
          fieldSchema,
        ];
      }

      return acc;
    }, []),
  };

  const fieldTypes = fields.reduce((schema, field: Field) => {
    if (!fieldIsPresentationalOnly(field) && !field.hidden) {
      const getFieldSchema: (field: Field) => { type: GraphQLType } = fieldToSchemaMap[field.type];

      if (getFieldSchema) {
        const fieldSchema = getFieldSchema(field);

        if (fieldHasSubFields(field) && Array.isArray(fieldSchema)) {
          return fieldSchema.reduce((acc, subField, i) => {
            const currentSubField = field.fields[i];
            if (fieldAffectsData(currentSubField)) {
              return {
                ...acc,
                [currentSubField.name]: subField,
              };
            }

            return {
              ...acc,
              ...fieldSchema,
            };
          }, schema);
        }

        if (fieldAffectsData(field)) {
          return {
            ...schema,
            [field.name]: fieldSchema,
          };
        }

        return {
          ...schema,
          ...fieldSchema,
        };
      }
    }

    return schema;
  }, {});

  const fieldName = formatName(name);

  return new GraphQLInputObjectType({
    name: `mutation${fieldName}Input`,
    fields: {
      ...fieldTypes,
    },
  });
}
Example #24
Source File: init.ts    From payload with MIT License 4 votes vote down vote up
function registerGlobals() {
  if (this.config.globals) {
    const {
      findOne, update, findVersionByID, findVersions, restoreVersion,
    } = this.graphQL.resolvers.globals;

    Object.keys(this.globals.config).forEach((slug) => {
      const global = this.globals.config[slug];
      const {
        label,
        fields,
      } = global;

      const formattedLabel = formatName(label);

      global.graphQL = {};

      global.graphQL.type = this.buildObjectType(
        formattedLabel,
        fields,
        formattedLabel,
      );

      global.graphQL.mutationInputType = new GraphQLNonNull(this.buildMutationInputType(
        formattedLabel,
        fields,
        formattedLabel,
      ));

      this.Query.fields[formattedLabel] = {
        type: global.graphQL.type,
        args: {
          draft: { type: GraphQLBoolean },
          ...(this.config.localization ? {
            locale: { type: this.types.localeInputType },
            fallbackLocale: { type: this.types.fallbackLocaleInputType },
          } : {}),
        },
        resolve: findOne(global),
      };

      this.Mutation.fields[`update${formattedLabel}`] = {
        type: global.graphQL.type,
        args: {
          data: { type: global.graphQL.mutationInputType },
          draft: { type: GraphQLBoolean },
        },
        resolve: update(global),
      };

      if (global.versions) {
        const versionGlobalFields = [
          ...buildVersionGlobalFields(global),
          {
            name: 'id',
            type: 'text',
          },
          {
            name: 'createdAt',
            label: 'Created At',
            type: 'date',
          },
          {
            name: 'updatedAt',
            label: 'Updated At',
            type: 'date',
          },
        ];
        global.graphQL.versionType = this.buildObjectType(
          `${formattedLabel}Version`,
          versionGlobalFields,
          `${formattedLabel}Version`,
          {},
        );
        this.Query.fields[`version${formatName(formattedLabel)}`] = {
          type: global.graphQL.versionType,
          args: {
            id: { type: GraphQLString },
            ...(this.config.localization ? {
              locale: { type: this.types.localeInputType },
              fallbackLocale: { type: this.types.fallbackLocaleInputType },
            } : {}),
          },
          resolve: findVersionByID(global),
        };
        this.Query.fields[`versions${formattedLabel}`] = {
          type: buildPaginatedListType(`versions${formatName(formattedLabel)}`, global.graphQL.versionType),
          args: {
            where: {
              type: this.buildWhereInputType(
                `versions${formattedLabel}`,
                versionGlobalFields,
                `versions${formattedLabel}`,
              ),
            },
            ...(this.config.localization ? {
              locale: { type: this.types.localeInputType },
              fallbackLocale: { type: this.types.fallbackLocaleInputType },
            } : {}),
            page: { type: GraphQLInt },
            limit: { type: GraphQLInt },
            sort: { type: GraphQLString },
          },
          resolve: findVersions(global),
        };
        this.Mutation.fields[`restoreVersion${formatName(formattedLabel)}`] = {
          type: global.graphQL.type,
          args: {
            id: { type: GraphQLString },
          },
          resolve: restoreVersion(global),
        };
      }
    });
  }
}
Example #25
Source File: init.ts    From payload with MIT License 4 votes vote down vote up
function registerCollections(): void {
  const {
    findVersions, findVersionByID, restoreVersion,
    create, find, findByID, deleteResolver, update,
  } = this.graphQL.resolvers.collections;

  const {
    login, logout, me, init, refresh, forgotPassword, resetPassword, verifyEmail, unlock,
  } = this.graphQL.resolvers.collections.auth;

  Object.keys(this.collections).forEach((slug) => {
    const collection = this.collections[slug];
    const {
      config: {
        labels: {
          singular,
          plural,
        },
        fields,
        timestamps,
      },
    } = collection;

    const singularLabel = formatName(singular);
    let pluralLabel = formatName(plural);

    // For collections named 'Media' or similar,
    // there is a possibility that the singular name
    // will equal the plural name. Append `all` to the beginning
    // of potential conflicts

    if (singularLabel === pluralLabel) {
      pluralLabel = `all${singularLabel}`;
    }

    collection.graphQL = {};

    const idField = fields.find(({ name }) => name === 'id');
    const idType = getCollectionIDType(collection.config);

    const baseFields: BaseFields = {};

    const whereInputFields = [
      ...fields,
    ];

    if (!idField) {
      baseFields.id = { type: idType };
      whereInputFields.push({
        name: 'id',
        type: 'text',
      });
    }

    if (timestamps) {
      baseFields.createdAt = {
        type: new GraphQLNonNull(DateTimeResolver),
      };

      baseFields.updatedAt = {
        type: new GraphQLNonNull(DateTimeResolver),
      };

      whereInputFields.push({
        name: 'createdAt',
        label: 'Created At',
        type: 'date',
      });

      whereInputFields.push({
        name: 'updatedAt',
        label: 'Updated At',
        type: 'date',
      });
    }

    collection.graphQL.type = this.buildObjectType(
      singularLabel,
      fields,
      singularLabel,
      baseFields,
    );

    collection.graphQL.whereInputType = this.buildWhereInputType(
      singularLabel,
      whereInputFields,
      singularLabel,
    );

    if (collection.config.auth) {
      fields.push({
        name: 'password',
        label: 'Password',
        type: 'text',
        required: true,
      });
    }

    collection.graphQL.mutationInputType = new GraphQLNonNull(this.buildMutationInputType(
      singularLabel,
      fields,
      singularLabel,
    ));

    collection.graphQL.updateMutationInputType = new GraphQLNonNull(this.buildMutationInputType(
      `${singularLabel}Update`,
      fields.filter((field) => field.name !== 'id'),
      `${singularLabel}Update`,
      true,
    ));

    this.Query.fields[singularLabel] = {
      type: collection.graphQL.type,
      args: {
        id: { type: idType },
        draft: { type: GraphQLBoolean },
        ...(this.config.localization ? {
          locale: { type: this.types.localeInputType },
          fallbackLocale: { type: this.types.fallbackLocaleInputType },
        } : {}),
      },
      resolve: findByID(collection),
    };

    this.Query.fields[pluralLabel] = {
      type: buildPaginatedListType(pluralLabel, collection.graphQL.type),
      args: {
        where: { type: collection.graphQL.whereInputType },
        draft: { type: GraphQLBoolean },
        ...(this.config.localization ? {
          locale: { type: this.types.localeInputType },
          fallbackLocale: { type: this.types.fallbackLocaleInputType },
        } : {}),
        page: { type: GraphQLInt },
        limit: { type: GraphQLInt },
        sort: { type: GraphQLString },
      },
      resolve: find(collection),
    };

    this.Mutation.fields[`create${singularLabel}`] = {
      type: collection.graphQL.type,
      args: {
        data: { type: collection.graphQL.mutationInputType },
        draft: { type: GraphQLBoolean },
      },
      resolve: create(collection),
    };

    this.Mutation.fields[`update${singularLabel}`] = {
      type: collection.graphQL.type,
      args: {
        id: { type: new GraphQLNonNull(idType) },
        data: { type: collection.graphQL.updateMutationInputType },
        draft: { type: GraphQLBoolean },
        autosave: { type: GraphQLBoolean },
      },
      resolve: update(collection),
    };

    this.Mutation.fields[`delete${singularLabel}`] = {
      type: collection.graphQL.type,
      args: {
        id: { type: new GraphQLNonNull(idType) },
      },
      resolve: deleteResolver(collection),
    };

    if (collection.config.versions) {
      const versionCollectionFields = [
        ...buildVersionCollectionFields(collection.config),
        {
          name: 'id',
          type: 'text',
        },
        {
          name: 'createdAt',
          label: 'Created At',
          type: 'date',
        },
        {
          name: 'updatedAt',
          label: 'Updated At',
          type: 'date',
        },
      ];
      collection.graphQL.versionType = this.buildObjectType(
        `${singularLabel}Version`,
        versionCollectionFields,
        `${singularLabel}Version`,
        {},
      );
      this.Query.fields[`version${formatName(singularLabel)}`] = {
        type: collection.graphQL.versionType,
        args: {
          id: { type: GraphQLString },
          ...(this.config.localization ? {
            locale: { type: this.types.localeInputType },
            fallbackLocale: { type: this.types.fallbackLocaleInputType },
          } : {}),
        },
        resolve: findVersionByID(collection),
      };
      this.Query.fields[`versions${pluralLabel}`] = {
        type: buildPaginatedListType(`versions${formatName(pluralLabel)}`, collection.graphQL.versionType),
        args: {
          where: {
            type: this.buildWhereInputType(
              `versions${singularLabel}`,
              versionCollectionFields,
              `versions${singularLabel}`,
            ),
          },
          ...(this.config.localization ? {
            locale: { type: this.types.localeInputType },
            fallbackLocale: { type: this.types.fallbackLocaleInputType },
          } : {}),
          page: { type: GraphQLInt },
          limit: { type: GraphQLInt },
          sort: { type: GraphQLString },
        },
        resolve: findVersions(collection),
      };
      this.Mutation.fields[`restoreVersion${formatName(singularLabel)}`] = {
        type: collection.graphQL.type,
        args: {
          id: { type: GraphQLString },
        },
        resolve: restoreVersion(collection),
      };
    }

    if (collection.config.auth) {
      collection.graphQL.JWT = this.buildObjectType(
        formatName(`${slug}JWT`),
        collection.config.fields.filter((field) => field.saveToJWT).concat([
          {
            name: 'email',
            type: 'email',
            required: true,
          },
          {
            name: 'collection',
            type: 'text',
            required: true,
          },
        ]),
        formatName(`${slug}JWT`),
      );

      this.Query.fields[`me${singularLabel}`] = {
        type: new GraphQLObjectType({
          name: formatName(`${slug}Me`),
          fields: {
            token: {
              type: GraphQLString,
            },
            user: {
              type: collection.graphQL.type,
            },
            exp: {
              type: GraphQLInt,
            },
            collection: {
              type: GraphQLString,
            },
          },
        }),
        resolve: me(slug),
      };

      if (collection.config.auth.maxLoginAttempts > 0) {
        this.Mutation.fields[`unlock${singularLabel}`] = {
          type: new GraphQLNonNull(GraphQLBoolean),
          args: {
            email: { type: new GraphQLNonNull(GraphQLString) },
          },
          resolve: unlock(collection),
        };
      }

      this.Query.fields[`initialized${singularLabel}`] = {
        type: GraphQLBoolean,
        resolve: init(collection),
      };

      this.Mutation.fields[`login${singularLabel}`] = {
        type: new GraphQLObjectType({
          name: formatName(`${slug}LoginResult`),
          fields: {
            token: {
              type: GraphQLString,
            },
            user: {
              type: collection.graphQL.type,
            },
            exp: {
              type: GraphQLInt,
            },
          },
        }),
        args: {
          email: { type: GraphQLString },
          password: { type: GraphQLString },
        },
        resolve: login(collection),
      };

      this.Mutation.fields[`logout${singularLabel}`] = {
        type: GraphQLString,
        resolve: logout(collection),
      };

      this.Mutation.fields[`forgotPassword${singularLabel}`] = {
        type: new GraphQLNonNull(GraphQLBoolean),
        args: {
          email: { type: new GraphQLNonNull(GraphQLString) },
          disableEmail: { type: GraphQLBoolean },
          expiration: { type: GraphQLInt },
        },
        resolve: forgotPassword(collection),
      };

      this.Mutation.fields[`resetPassword${singularLabel}`] = {
        type: new GraphQLObjectType({
          name: formatName(`${slug}ResetPassword`),
          fields: {
            token: { type: GraphQLString },
            user: { type: collection.graphQL.type },
          },
        }),
        args: {
          token: { type: GraphQLString },
          password: { type: GraphQLString },
        },
        resolve: resetPassword(collection),
      };

      this.Mutation.fields[`verifyEmail${singularLabel}`] = {
        type: GraphQLBoolean,
        args: {
          token: { type: GraphQLString },
        },
        resolve: verifyEmail(collection),
      };

      this.Mutation.fields[`refreshToken${singularLabel}`] = {
        type: new GraphQLObjectType({
          name: formatName(`${slug}Refreshed${singularLabel}`),
          fields: {
            user: {
              type: collection.graphQL.JWT,
            },
            refreshedToken: {
              type: GraphQLString,
            },
            exp: {
              type: GraphQLInt,
            },
          },
        }),
        args: {
          token: { type: GraphQLString },
        },
        resolve: refresh(collection),
      };
    }
  });
}
Example #26
Source File: getComposerFromJSONSchema.ts    From graphql-mesh with MIT License 4 votes vote down vote up
export function getComposerFromJSONSchema(
  schema: JSONSchema,
  logger: Logger,
  generateInterfaceFromSharedFields = false
): Promise<TypeComposers> {
  const schemaComposer = new SchemaComposer();
  const ajv = new Ajv({
    strict: false,
  });
  addFormats(ajv);
  const formatScalarMap = getJSONSchemaStringFormatScalarMap(ajv);
  const futureTasks = new Set<VoidFunction>();
  return visitJSONSchema(schema, function mutateFn(subSchema, { path }): any {
    logger?.debug(`Processing ${path} for GraphQL Schema`);
    const getTypeComposer = (): any => {
      if (typeof subSchema === 'boolean') {
        const typeComposer = schemaComposer.getAnyTC(GraphQLJSON);
        return subSchema
          ? {
              input: typeComposer,
              output: typeComposer,
            }
          : undefined;
      }
      const validateWithJSONSchema = getValidateFnForSchemaPath(ajv, path, schema);

      if (!subSchema) {
        throw new Error(`Something is wrong with ${path}`);
      }

      if (subSchema.pattern) {
        const scalarType = new RegularExpression(
          getValidTypeName({
            schemaComposer,
            isInput: false,
            subSchema,
          }),
          new RegExp(subSchema.pattern),
          {
            description: subSchema.description,
          }
        );
        const typeComposer = schemaComposer.getAnyTC(scalarType);
        return {
          input: typeComposer,
          output: typeComposer,
        };
      }
      if (subSchema.const) {
        const tsTypeName = JSON.stringify(subSchema.const);
        const scalarTypeName = getValidTypeName({
          schemaComposer,
          isInput: false,
          subSchema,
        });
        const scalarType = new RegularExpression(scalarTypeName, new RegExp(subSchema.const), {
          description: subSchema.description || `A field whose value is ${tsTypeName}`,
          errorMessage: (_r, v: string) => `Expected ${tsTypeName} but got ${JSON.stringify(v)}`,
        });
        scalarType.extensions = {
          codegenScalarType: tsTypeName,
        };
        const typeComposer = schemaComposer.createScalarTC(scalarType);
        return {
          input: typeComposer,
          output: typeComposer,
        };
      }
      if (subSchema.enum && subSchema.type !== 'boolean') {
        const values: Record<string, EnumTypeComposerValueConfigDefinition> = {};
        for (const value of subSchema.enum) {
          let enumKey = sanitizeNameForGraphQL(value.toString());
          if (enumKey === 'false' || enumKey === 'true' || enumKey === 'null') {
            enumKey = enumKey.toUpperCase();
          }
          if (typeof enumKey === 'string' && enumKey.length === 0) {
            enumKey = '_';
          }
          values[enumKey] = {
            // Falsy values are ignored by GraphQL
            // eslint-disable-next-line no-unneeded-ternary
            value: value ? value : value?.toString(),
          };
        }
        const typeComposer = schemaComposer.createEnumTC({
          name: getValidTypeName({
            schemaComposer,
            isInput: false,
            subSchema,
          }),
          values,
          description: subSchema.description,
          extensions: {
            examples: subSchema.examples,
            default: subSchema.default,
          },
        });
        return {
          input: typeComposer,
          output: typeComposer,
        };
      }

      if (subSchema.oneOf && !subSchema.properties) {
        let statusCodeOneOfIndexMap: Record<string, number> | undefined;
        if (subSchema.$comment?.startsWith('statusCodeOneOfIndexMap:')) {
          const statusCodeOneOfIndexMapStr = subSchema.$comment.replace('statusCodeOneOfIndexMap:', '');
          statusCodeOneOfIndexMap = JSON.parse(statusCodeOneOfIndexMapStr);
        }
        const isPlural = (subSchema.oneOf as TypeComposers[]).some(({ output }) => 'ofType' in output);
        if (isPlural) {
          const { input, output } = getUnionTypeComposers({
            schemaComposer,
            ajv,
            typeComposersList: (subSchema.oneOf as any).map(({ input, output }: any) => ({
              input: input.ofType || input,
              output: output.ofType || output,
            })) as any[],
            subSchema,
            generateInterfaceFromSharedFields,
            statusCodeOneOfIndexMap,
          });
          return {
            input: input.getTypePlural(),
            output: output.getTypePlural(),
          };
        }
        return getUnionTypeComposers({
          schemaComposer,
          ajv,
          typeComposersList: subSchema.oneOf as any[],
          subSchema,
          generateInterfaceFromSharedFields,
          statusCodeOneOfIndexMap,
        });
      }

      if (subSchema.allOf && !subSchema.properties) {
        const inputFieldMap: InputTypeComposerFieldConfigMap = {};
        const fieldMap: ObjectTypeComposerFieldConfigMap<any, any> = {};
        let ableToUseGraphQLInputObjectType = true;
        for (const maybeTypeComposers of subSchema.allOf as any) {
          const { input: inputTypeComposer, output: outputTypeComposer } = maybeTypeComposers;

          if (inputTypeComposer instanceof ScalarTypeComposer) {
            ableToUseGraphQLInputObjectType = false;
          } else {
            const inputTypeElemFieldMap = inputTypeComposer.getFields();
            for (const fieldName in inputTypeElemFieldMap) {
              const field = inputTypeElemFieldMap[fieldName];
              inputFieldMap[fieldName] = field;
            }
          }

          if (outputTypeComposer instanceof ScalarTypeComposer) {
            fieldMap[outputTypeComposer.getTypeName()] = {
              type: outputTypeComposer,
              resolve: root => root,
            };
          } else if (outputTypeComposer instanceof UnionTypeComposer) {
            const outputTCElems = outputTypeComposer.getTypes() as ObjectTypeComposer[];
            for (const outputTCElem of outputTCElems) {
              const outputTypeElemFieldMap = outputTCElem.getFields();
              for (const fieldName in outputTypeElemFieldMap) {
                const field = outputTypeElemFieldMap[fieldName];
                fieldMap[fieldName] = field;
              }
            }
          } else {
            const typeElemFieldMap = outputTypeComposer.getFields();
            for (const fieldName in typeElemFieldMap) {
              const field = typeElemFieldMap[fieldName];
              fieldMap[fieldName] = field;
            }
          }
        }

        let inputTypeComposer;
        const outputTypeComposer = schemaComposer.createObjectTC({
          name: getValidTypeName({
            schemaComposer,
            isInput: false,
            subSchema,
          }),
          description: subSchema.description,
          fields: fieldMap,
          extensions: {
            validateWithJSONSchema,
            examples: subSchema.examples,
            default: subSchema.default,
          },
        });

        if (ableToUseGraphQLInputObjectType) {
          inputTypeComposer = schemaComposer.createInputTC({
            name: getValidTypeName({
              schemaComposer,
              isInput: true,
              subSchema,
            }),
            description: subSchema.description,
            fields: inputFieldMap,
            extensions: {
              examples: subSchema.examples,
              default: subSchema.default,
            },
          });
        } else {
          inputTypeComposer = isSomeInputTypeComposer(outputTypeComposer)
            ? outputTypeComposer
            : getGenericJSONScalar({
                schemaComposer,
                isInput: true,
                subSchema,
                validateWithJSONSchema,
              });
        }

        return {
          input: inputTypeComposer,
          output: outputTypeComposer,
        };
      }

      if (subSchema.anyOf && !subSchema.properties) {
        if (subSchema.title === 'Any') {
          const genericJSONScalar = getGenericJSONScalar({
            schemaComposer,
            isInput: false,
            subSchema,
            validateWithJSONSchema,
          });
          return {
            input: genericJSONScalar,
            output: genericJSONScalar,
          };
        }
        // It should not have `required` because it is `anyOf` not `allOf`
        const inputFieldMap: InputTypeComposerFieldConfigMap = {};
        const fieldMap: ObjectTypeComposerFieldConfigMap<any, any> = {};
        let ableToUseGraphQLInputObjectType = true;
        for (const typeComposers of subSchema.anyOf as any) {
          const { input: inputTypeComposer, output: outputTypeComposer } = typeComposers;
          if (inputTypeComposer instanceof ScalarTypeComposer) {
            ableToUseGraphQLInputObjectType = false;
          } else {
            const inputTypeElemFieldMap = inputTypeComposer.getFields();
            for (const fieldName in inputTypeElemFieldMap) {
              const field = inputTypeElemFieldMap[fieldName];
              inputFieldMap[fieldName] = isNonNullType(field.type.getType())
                ? {
                    ...field,
                    type: () => field.type.ofType,
                  }
                : field;
            }
          }

          if (outputTypeComposer instanceof ScalarTypeComposer) {
            const typeName = outputTypeComposer.getTypeName();
            fieldMap[typeName] = {
              type: outputTypeComposer,
              resolve: root => root,
            };
          } else {
            const typeElemFieldMap = outputTypeComposer.getFields();
            for (const fieldName in typeElemFieldMap) {
              const field = typeElemFieldMap[fieldName];
              fieldMap[fieldName] = {
                type: () => getNamedType(field.type.getType()),
                ...field,
              };
            }
          }
        }

        let inputTypeComposer;
        const outputTypeComposer = schemaComposer.createObjectTC({
          name: getValidTypeName({
            schemaComposer,
            isInput: false,
            subSchema,
          }),
          description: subSchema.description,
          fields: fieldMap,
          extensions: {
            validateWithJSONSchema,
            examples: subSchema.examples,
            default: subSchema.default,
          },
        });

        if (ableToUseGraphQLInputObjectType) {
          inputTypeComposer = schemaComposer.createInputTC({
            name: getValidTypeName({
              schemaComposer,
              isInput: true,
              subSchema,
            }),
            description: subSchema.description,
            fields: inputFieldMap,
            extensions: {
              examples: subSchema.examples,
              default: subSchema.default,
            },
          });
        } else {
          inputTypeComposer = isSomeInputTypeComposer(outputTypeComposer)
            ? outputTypeComposer
            : getGenericJSONScalar({
                schemaComposer,
                isInput: true,
                subSchema,
                validateWithJSONSchema,
              });
        }

        return {
          input: inputTypeComposer,
          output: outputTypeComposer,
        };
      }

      if (Array.isArray(subSchema.type)) {
        const validTypes = subSchema.type.filter((typeName: string) => typeName !== 'null');
        if (validTypes.length === 1) {
          subSchema.type = validTypes[0];
          // continue with the single type
        } else {
          const typeComposer = schemaComposer.getAnyTC(GraphQLVoid);
          return {
            input: typeComposer,
            output: typeComposer,
          };
        }
      }

      switch (subSchema.type as any) {
        case 'file': {
          const typeComposer = schemaComposer.getAnyTC(GraphQLFile);
          return {
            input: typeComposer,
            output: typeComposer,
            description: subSchema.description,
          };
        }
        case 'boolean': {
          const typeComposer = schemaComposer.getAnyTC(GraphQLBoolean);
          return {
            input: typeComposer,
            output: typeComposer,
            description: subSchema.description,
          };
        }
        case 'null': {
          const typeComposer = schemaComposer.getAnyTC(GraphQLVoid);
          return {
            input: typeComposer,
            output: typeComposer,
            description: subSchema.description,
          };
        }
        case 'integer': {
          if (subSchema.format === 'int64') {
            const typeComposer = schemaComposer.getAnyTC(GraphQLBigInt);
            return {
              input: typeComposer,
              output: typeComposer,
              description: subSchema.description,
            };
          }
          const typeComposer = schemaComposer.getAnyTC(GraphQLInt);
          return {
            input: typeComposer,
            output: typeComposer,
            description: subSchema.description,
          };
        }
        case 'number': {
          const typeComposer = schemaComposer.getAnyTC(GraphQLFloat);
          return {
            input: typeComposer,
            output: typeComposer,
            description: subSchema.description,
          };
        }
        case 'string': {
          if (subSchema.minLength || subSchema.maxLength) {
            const scalarType = getStringScalarWithMinMaxLength({
              schemaComposer,
              subSchema,
            });
            const typeComposer = schemaComposer.getAnyTC(scalarType);
            return {
              input: typeComposer,
              output: typeComposer,
              description: subSchema.description,
            };
          }
          switch (subSchema.format) {
            case 'date-time': {
              const typeComposer = schemaComposer.getAnyTC(GraphQLDateTime);
              return {
                input: typeComposer,
                output: typeComposer,
                description: subSchema.description,
              };
            }
            case 'time': {
              const typeComposer = schemaComposer.getAnyTC(GraphQLTime);
              return {
                input: typeComposer,
                output: typeComposer,
                description: subSchema.description,
              };
            }
            case 'email': {
              const typeComposer = schemaComposer.getAnyTC(GraphQLEmailAddress);
              return {
                input: typeComposer,
                output: typeComposer,
                description: subSchema.description,
              };
            }
            case 'ipv4': {
              const typeComposer = schemaComposer.getAnyTC(GraphQLIPv4);
              return {
                input: typeComposer,
                output: typeComposer,
                description: subSchema.description,
              };
            }
            case 'ipv6': {
              const typeComposer = schemaComposer.getAnyTC(GraphQLIPv6);
              return {
                input: typeComposer,
                output: typeComposer,
                description: subSchema.description,
              };
            }
            case 'uri': {
              const typeComposer = schemaComposer.getAnyTC(GraphQLURL);
              return {
                input: typeComposer,
                output: typeComposer,
                description: subSchema.description,
              };
            }
            default: {
              const formatScalar = formatScalarMap.get(subSchema.format) || GraphQLString;
              const typeComposer = schemaComposer.getAnyTC(formatScalar);
              return {
                input: typeComposer,
                output: typeComposer,
                description: subSchema.description,
              };
            }
          }
        }
        case 'array':
          if (
            typeof subSchema.items === 'object' &&
            !Array.isArray(subSchema.items) &&
            Object.keys(subSchema.items).length > 0
          ) {
            const typeComposers = subSchema.items;
            return {
              input: typeComposers.input.getTypePlural(),
              output: typeComposers.output.getTypePlural(),
              description: subSchema.description,
            };
          }
          if (subSchema.contains) {
            // Scalars cannot be in union type
            const typeComposer = getGenericJSONScalar({
              schemaComposer,
              isInput: false,
              subSchema,
              validateWithJSONSchema,
            }).getTypePlural();
            return {
              input: typeComposer,
              output: typeComposer,
            };
          }
          if (typeof subSchema.items === 'object' && Array.isArray(subSchema.items)) {
            const existingItems = [...(subSchema.items as any)];
            /* TODO
            if (subSchema.additionalItems) {
              existingItems.push(subSchema.additionalItems);
            }
            */
            const { input: inputTypeComposer, output: outputTypeComposer } = getUnionTypeComposers({
              schemaComposer,
              ajv,
              typeComposersList: existingItems,
              subSchema,
              generateInterfaceFromSharedFields,
            });
            return {
              input: inputTypeComposer.getTypePlural(),
              output: outputTypeComposer.getTypePlural(),
              description: subSchema.description,
            };
          }
          // If it doesn't have any clue
          {
            // const typeComposer = getGenericJSONScalar({
            //   schemaComposer,
            //   isInput: false,
            //   subSchema,
            //   validateWithJSONSchema,
            // }).getTypePlural();
            const typeComposer = schemaComposer.getAnyTC(GraphQLJSON).getTypePlural();
            return {
              input: typeComposer,
              output: typeComposer,
              description: subSchema.description,
            };
          }
        case 'object':
          const fieldMap: ObjectTypeComposerFieldConfigMapDefinition<any, any> = {};
          let inputFieldMap: Record<string, InputTypeComposerFieldConfigAsObjectDefinition & { type: any }> = {};
          if (subSchema.properties) {
            subSchema.type = 'object';
            for (const propertyName in subSchema.properties) {
              // TODO: needs to be fixed
              if (propertyName === 'additionalProperties') {
                continue;
              }
              const typeComposers = subSchema.properties[propertyName];
              const fieldName = sanitizeNameForGraphQL(propertyName);
              fieldMap[fieldName] = {
                type: () =>
                  subSchema.required?.includes(propertyName)
                    ? typeComposers.output.getTypeNonNull()
                    : typeComposers.output,
                // Make sure you get the right property
                resolve: root => {
                  const actualFieldObj = root[propertyName];
                  if (actualFieldObj != null) {
                    const isArray = Array.isArray(actualFieldObj);
                    const isListType = isListTC(typeComposers.output);
                    if (isListType && !isArray) {
                      return [actualFieldObj];
                    } else if (!isListTC(typeComposers.output) && isArray) {
                      return actualFieldObj[0];
                    }
                  }
                  return actualFieldObj;
                },
                description: typeComposers.description || typeComposers.output?.description,
              };
              inputFieldMap[fieldName] = {
                type: () =>
                  subSchema.required?.includes(propertyName)
                    ? typeComposers.input?.getTypeNonNull()
                    : typeComposers.input,
                // Let execution logic know what is the expected propertyName
                extensions: {
                  propertyName,
                },
                description: typeComposers.description || typeComposers.input?.description,
                defaultValue: typeComposers?.extensions?.default || typeComposers.input?.default,
              };
            }
          }

          if (subSchema.allOf) {
            for (const typeComposers of subSchema.allOf) {
              const outputTC: ObjectTypeComposer = (typeComposers as any).output;
              if (schemaComposer.isObjectType(outputTC)) {
                for (const outputFieldName of outputTC.getFieldNames()) {
                  if (!fieldMap[outputFieldName]) {
                    fieldMap[outputFieldName] = outputTC.getField(outputFieldName);
                  }
                }
              }
              const inputTC: InputTypeComposer = (typeComposers as any).input;
              if (schemaComposer.isInputObjectType(inputTC)) {
                for (const inputFieldName of inputTC.getFieldNames()) {
                  if (!inputFieldMap[inputFieldName]) {
                    inputFieldMap[inputFieldName] = inputTC.getField(inputFieldName);
                  }
                }
              }
            }
          }

          if (subSchema.additionalProperties) {
            if (
              typeof subSchema.additionalProperties === 'object' &&
              subSchema.additionalProperties.output instanceof ObjectTypeComposer
            ) {
              if (Object.keys(fieldMap).length === 0) {
                return subSchema.additionalProperties;
              } else {
                const outputTC: ObjectTypeComposer = (subSchema.additionalProperties as any).output;
                const outputTCFieldMap = outputTC.getFields();
                for (const fieldName in outputTCFieldMap) {
                  fieldMap[fieldName] = outputTCFieldMap[fieldName];
                }
                const inputTC: InputTypeComposer = (subSchema.additionalProperties as any).input;
                const inputTCFieldMap = inputTC.getFields();
                for (const fieldName in inputTCFieldMap) {
                  inputFieldMap[fieldName] = inputTCFieldMap[fieldName];
                }
              }
            } else {
              fieldMap.additionalProperties = {
                type: GraphQLJSON,
                resolve: (root: any) => root,
              };
              inputFieldMap = {};
            }
          }

          if (subSchema.title === '_schema') {
            futureTasks.forEach(futureTask => futureTask());
            return {
              output: schemaComposer,
            };
          }

          if (subSchema.title === 'Query') {
            const typeComposer = schemaComposer.Query;
            typeComposer.addFields(fieldMap);
            return {
              output: typeComposer,
            };
          }

          if (subSchema.title === 'Mutation') {
            const typeComposer = schemaComposer.Mutation;
            typeComposer.addFields(fieldMap);
            return {
              output: typeComposer,
            };
          }

          if (subSchema.title === 'Subscription') {
            const typeComposer = schemaComposer.Subscription;
            typeComposer.addFields(fieldMap);
            return {
              output: typeComposer,
            };
          }

          const getCorrectInputFieldType = (fieldName: string) => {
            const inputType: InputTypeComposer | ListComposer<InputTypeComposer> = inputFieldMap[fieldName].type();
            const actualInputType = isListTC(inputType) ? inputType.ofType : inputType;
            if (!actualInputType.getFields) {
              return actualInputType;
            }
            const fieldMap = actualInputType.getFields();
            for (const fieldName in fieldMap) {
              const fieldConfig = fieldMap[fieldName];
              if (fieldConfig.type.getTypeName().endsWith('!')) {
                return inputType.getTypeNonNull();
              }
            }
            return inputType;
          };

          if (subSchema.title === 'QueryInput') {
            const typeComposer = schemaComposer.Query;
            for (const fieldName in inputFieldMap) {
              futureTasks.add(() =>
                typeComposer.addFieldArgs(fieldName, {
                  input: {
                    type: () => getCorrectInputFieldType(fieldName),
                    description: inputFieldMap[fieldName].description,
                  },
                })
              );
            }
            return {
              output: typeComposer,
            };
          }

          if (subSchema.title === 'MutationInput') {
            const typeComposer = schemaComposer.Mutation;
            for (const fieldName in inputFieldMap) {
              futureTasks.add(() =>
                typeComposer.addFieldArgs(fieldName, {
                  input: {
                    type: () => getCorrectInputFieldType(fieldName),
                    description: inputFieldMap[fieldName].description,
                  },
                })
              );
            }
            return {
              output: typeComposer,
            };
          }

          if (subSchema.title === 'SubscriptionInput') {
            const typeComposer = schemaComposer.Subscription;
            for (const fieldName in inputFieldMap) {
              futureTasks.add(() =>
                typeComposer.addFieldArgs(fieldName, {
                  input: {
                    type: () => getCorrectInputFieldType(fieldName),
                    description: inputFieldMap[fieldName].description,
                  },
                })
              );
            }
            return {
              output: typeComposer,
            };
          }

          const output =
            Object.keys(fieldMap).length === 0
              ? getGenericJSONScalar({
                  schemaComposer,
                  isInput: false,
                  subSchema,
                  validateWithJSONSchema,
                })
              : schemaComposer.createObjectTC({
                  name: getValidTypeName({
                    schemaComposer,
                    isInput: false,
                    subSchema,
                  }),
                  description: subSchema.description,
                  fields: fieldMap,
                  extensions: {
                    validateWithJSONSchema,
                    examples: subSchema.examples,
                    default: subSchema.default,
                  },
                });

          const input =
            Object.keys(inputFieldMap).length === 0
              ? getGenericJSONScalar({
                  schemaComposer,
                  isInput: true,
                  subSchema,
                  validateWithJSONSchema,
                })
              : schemaComposer.createInputTC({
                  name: getValidTypeName({
                    schemaComposer,
                    isInput: true,
                    subSchema,
                  }),
                  description: subSchema.description,
                  fields: inputFieldMap,
                  extensions: {
                    examples: subSchema.examples,
                    default: subSchema.default,
                  },
                });

          return {
            input,
            output,
          };
      }
      logger.warn(`GraphQL Type cannot be created for this JSON Schema definition;`, {
        subSchema,
        path,
      });
      const typeComposer = schemaComposer.getAnyTC(GraphQLJSON);
      return {
        input: typeComposer,
        output: typeComposer,
      };
    };
    const result = getTypeComposer();
    return result;
  });
}
Example #27
Source File: getComposerFromSchema.test.ts    From graphql-mesh with MIT License 4 votes vote down vote up
describe('getComposerFromJSONSchema', () => {
  const logger = new DefaultLogger('getComposerFromJSONSchema - test');
  it('should return JSON scalar if given schema is boolean true', async () => {
    const result = await getComposerFromJSONSchema(true, logger);
    expect(result.input.getType()).toBe(GraphQLJSON);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLJSON);
  });
  it('should generate a new scalar type that validates the value against the given pattern in string schema', async () => {
    const pattern = '^\\d{10}$';
    const title = 'ExampleRegEx';
    const inputSchema: JSONSchema = {
      title,
      type: 'string',
      pattern,
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    // Scalar types are both input and output types
    expect(result.input).toBe(result.output);
    const outputComposer = result.output as ScalarTypeComposer;
    expect(isScalarType(outputComposer.getType())).toBeTruthy();
    expect(outputComposer.getTypeName()).toBe(title);
    const serializeFn = outputComposer.getSerialize();
    expect(() => serializeFn('not-valid-phone-number')).toThrow();
    expect(serializeFn('1231231234')).toBe('1231231234');
  });
  it('should generate a new scalar type that validates the value against the given const in string schema', async () => {
    const constStr = 'FOO';
    const title = 'ExampleConst';
    const inputSchema: JSONSchema = {
      title,
      type: 'string',
      const: constStr,
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    // Scalar types are both input and output types
    expect(result.input).toBe(result.output);
    const outputComposer = result.output as ScalarTypeComposer;
    expect(isScalarType(outputComposer.getType())).toBeTruthy();
    expect(outputComposer.getTypeName()).toBe(title);
    const serializeFn = outputComposer.getSerialize();
    expect(() => serializeFn('bar')).toThrow();
    expect(serializeFn(constStr)).toBe(constStr);
  });
  it('should generate a new enum type from enum schema', async () => {
    const enumValues = ['foo', 'bar', 'qux'];
    const title = 'ExampleEnum';
    const inputSchema: JSONSchema = {
      title,
      type: 'string',
      enum: enumValues,
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    // Enum types are both input and output types
    expect(result.input).toBe(result.output);
    const outputComposer = result.output as EnumTypeComposer;
    expect(outputComposer.toSDL()).toBe(
      /* GraphQL */ `
enum ExampleEnum {
  foo
  bar
  qux
}`.trim()
    );
  });
  it('should generate a new enum type from enum schema by sanitizing enum keys', async () => {
    const enumValues = ['0-foo', '1+bar', '2)qux'];
    const title = 'ExampleEnum';
    const inputSchema: JSONSchema = {
      title,
      type: 'string',
      enum: enumValues,
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    // Enum types are both input and output types
    expect(result.input).toBe(result.output);
    const outputComposer = result.output as EnumTypeComposer;
    expect(outputComposer.toSDL()).toMatchInlineSnapshot(`
      "enum ExampleEnum {
        _0_foo
        _1_PLUS_bar
        _2_RIGHT_PARENTHESIS_qux
      }"
    `);
  });
  it('should generate union types from oneOf object types', async () => {
    const inputSchema: JSONSchema = {
      title: 'User',
      type: 'object',
      oneOf: [
        {
          title: 'Writer',
          type: 'object',
          properties: {
            id: {
              type: 'string',
            },
            name: {
              type: 'string',
            },
            posts: {
              type: 'array',
              items: {
                title: 'Post',
                type: 'object',
                properties: {
                  id: {
                    type: 'string',
                  },
                  title: {
                    type: 'string',
                  },
                  content: {
                    type: 'string',
                  },
                },
              },
            },
          },
        },
        {
          title: 'Admin',
          type: 'object',
          properties: {
            id: {
              type: 'string',
            },
            name: {
              type: 'string',
            },
            permissions: {
              type: 'array',
              items: {
                title: 'AdminPermission',
                type: 'string',
                enum: ['edit', 'delete'],
              },
            },
          },
        },
      ],
    };
    const outputSchema = /* GraphQL */ `
union User = Writer | Admin

type Writer {
  id: String
  name: String
  posts: [Post]
}

${printType(GraphQLString)}

type Post {
  id: String
  title: String
  content: String
}

type Admin {
  id: String
  name: String
  permissions: [AdminPermission]
}

enum AdminPermission {
  edit
  delete
}
    `.trim();

    const result = await getComposerFromJSONSchema(inputSchema, logger);
    const unionComposer = result.output as UnionTypeComposer;
    expect(
      unionComposer.toSDL({
        deep: true,
      })
    ).toBe(outputSchema);
  });
  it('should generate an input union type for oneOf definitions that contain scalar types', async () => {
    const title = 'ExampleOneOf';
    const inputSchema: JSONSchema = {
      title,
      oneOf: [
        {
          type: 'string',
        },
        {
          type: 'object',
          title: 'ExampleObject',
          properties: {
            id: {
              type: 'string',
            },
          },
        },
      ],
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(
      (result.input as InputTypeComposer).toSDL({
        deep: true,
      })
    ).toBe(
      /* GraphQL */ `
input ExampleOneOf_Input @oneOf {
  String: String
  ExampleObject_Input: ExampleObject_Input
}

${printType(GraphQLString)}

input ExampleObject_Input {
  id: String
}
    `.trim()
    );
  });
  it('should generate merged object types from allOf definitions', async () => {
    const inputSchema: JSONSchema = {
      title: 'ExampleAllOf',
      allOf: [
        {
          type: 'object',
          title: 'Foo',
          properties: {
            id: {
              type: 'string',
            },
          },
          required: ['id'],
        },
        {
          type: 'object',
          title: 'Bar',
          properties: {
            name: {
              type: 'string',
            },
          },
          required: ['name'],
        },
      ],
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect((result.input as InputTypeComposer).toSDL()).toBe(
      /* GraphQL */ `
input ExampleAllOf_Input {
  id: String!
  name: String!
}
    `.trim()
    );
    expect((result.output as InputTypeComposer).toSDL()).toBe(
      /* GraphQL */ `
type ExampleAllOf {
  id: String!
  name: String!
}
    `.trim()
    );
  });
  it('should generate container types and fields for allOf definitions that contain scalar types', async () => {
    const title = 'ExampleAllOf';
    const inputSchema: JSONSchema = {
      title,
      allOf: [
        {
          type: 'string',
        },
        {
          type: 'object',
          title: 'ExampleObject',
          properties: {
            id: {
              type: 'string',
            },
          },
        },
      ],
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    const outputComposer = result.output as ObjectTypeComposer;
    expect(isObjectType(outputComposer.getType())).toBeTruthy();
    expect(outputComposer.getTypeName()).toBe(title);
    expect(outputComposer.getFieldNames().includes('String')).toBeTruthy();
    expect(outputComposer.getFieldNames().includes('id')).toBeTruthy();
  });
  it('should generate correct types for anyOf definitions', async () => {
    const inputSchema: JSONSchema = {
      title: 'ExampleAnyOf',
      anyOf: [
        {
          type: 'object',
          title: 'Foo',
          properties: {
            id: {
              type: 'string',
            },
          },
          required: ['id'],
        },
        {
          type: 'object',
          title: 'Bar',
          properties: {
            name: {
              type: 'string',
            },
          },
          required: ['name'],
        },
      ],
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect((result.input as InputTypeComposer).toSDL()).toBe(
      /* GraphQL */ `
input ExampleAnyOf_Input {
  id: String!
  name: String!
}
    `.trim()
    );
    expect((result.output as InputTypeComposer).toSDL()).toBe(
      /* GraphQL */ `
type ExampleAnyOf {
  id: String!
  name: String!
}
    `.trim()
    );
  });
  it('should generate container types and fields for anyOf definitions that contain scalar types', async () => {
    const title = 'ExampleAnyOf';
    const inputSchema: JSONSchema = {
      title,
      allOf: [
        {
          type: 'string',
        },
        {
          type: 'object',
          title: 'ExampleObject',
          properties: {
            id: {
              type: 'string',
            },
          },
        },
      ],
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    const outputComposer = result.output as ObjectTypeComposer;
    expect(isObjectType(outputComposer.getType())).toBeTruthy();
    expect(outputComposer.getTypeName()).toBe(title);
    expect(outputComposer.getFieldNames().includes('String')).toBeTruthy();
    expect(outputComposer.getFieldNames().includes('id')).toBeTruthy();
  });
  it('should return Boolean for boolean definition', async () => {
    const inputSchema: JSONSchema = {
      type: 'boolean',
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);

    expect(result.input.getType()).toBe(GraphQLBoolean);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLBoolean);
  });
  it('should return Void for null definition', async () => {
    const inputSchema: JSONSchema = {
      type: 'null',
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);

    expect(result.input.getType().name).toBe('Void');
    expect((result.output as ScalarTypeComposer).getType().name).toBe('Void');
  });
  it('should return BigInt for int64 definition', async () => {
    const inputSchema: JSONSchema = {
      type: 'integer',
      format: 'int64',
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);

    expect(result.input.getType()).toBe(GraphQLBigInt);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLBigInt);
  });
  it('should return Int for int32 definition', async () => {
    const inputSchema: JSONSchema = {
      type: 'integer',
      format: 'int32',
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);

    expect(result.input.getType()).toBe(GraphQLInt);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLInt);
  });
  it('should return Int for integer definitions without format', async () => {
    const inputSchema: JSONSchema = {
      type: 'integer',
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);

    expect(result.input.getType()).toBe(GraphQLInt);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLInt);
  });
  it('should return Float for number definition', async () => {
    const inputSchema: JSONSchema = {
      type: 'number',
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);

    expect(result.input.getType()).toBe(GraphQLFloat);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLFloat);
  });
  it('should generate scalar types for minLength definition', async () => {
    const title = 'NonEmptyString';
    const inputSchema: JSONSchema = {
      title,
      type: 'string',
      minLength: 1,
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    const inputComposer = result.input as ScalarTypeComposer;
    expect(inputComposer).toBe(result.output);
    expect(inputComposer.getTypeName()).toBe(title);
    const serializeFn = inputComposer.getSerialize();
    expect(() => serializeFn('')).toThrow();
    expect(serializeFn('aa')).toBe('aa');
  });
  it('should generate scalar types for maxLength definition', async () => {
    const title = 'NonEmptyString';
    const inputSchema: JSONSchema = {
      title,
      type: 'string',
      maxLength: 2,
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    const inputComposer = result.input as ScalarTypeComposer;
    expect(inputComposer).toBe(result.output);
    expect(inputComposer.getTypeName()).toBe(title);
    const serializeFn = inputComposer.getSerialize();
    expect(() => serializeFn('aaa')).toThrow();
    expect(serializeFn('a')).toBe('a');
  });
  it('should generate scalar types for both minLength and maxLength definition', async () => {
    const title = 'NonEmptyString';
    const inputSchema: JSONSchema = {
      title,
      type: 'string',
      minLength: 1,
      maxLength: 2,
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    const inputComposer = result.input as ScalarTypeComposer;
    expect(inputComposer).toBe(result.output);
    expect(inputComposer.getTypeName()).toBe(title);
    const serializeFn = inputComposer.getSerialize();
    expect(() => serializeFn('aaa')).toThrow();
    expect(() => serializeFn('')).toThrow();
    expect(serializeFn('a')).toBe('a');
  });
  it('should return DateTime scalar for date-time format', async () => {
    const inputSchema: JSONSchema = {
      type: 'string',
      format: 'date-time',
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input.getType()).toBe(GraphQLDateTime);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLDateTime);
  });
  it('should return Time scalar for time format', async () => {
    const inputSchema: JSONSchema = {
      type: 'string',
      format: 'time',
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input.getType()).toBe(GraphQLTime);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLTime);
  });
  it('should return EmailAddress scalar for email format', async () => {
    const inputSchema: JSONSchema = {
      type: 'string',
      format: 'email',
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input.getType()).toBe(GraphQLEmailAddress);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLEmailAddress);
  });
  it('should return IPv4 scalar for email format', async () => {
    const inputSchema: JSONSchema = {
      type: 'string',
      format: 'ipv4',
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input.getType()).toBe(GraphQLIPv4);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLIPv4);
  });
  it('should return IPv6 scalar for email format', async () => {
    const inputSchema: JSONSchema = {
      type: 'string',
      format: 'ipv6',
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input.getType()).toBe(GraphQLIPv6);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLIPv6);
  });
  it('should return URL scalar for uri format', async () => {
    const inputSchema: JSONSchema = {
      type: 'string',
      format: 'uri',
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input.getType()).toBe(GraphQLURL);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLURL);
  });
  it('should return String for string definitions without format', async () => {
    const inputSchema: JSONSchema = {
      type: 'string',
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input.getType()).toBe(GraphQLString);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLString);
  });
  it('should return list type for array definitions with items as object', async () => {
    const inputSchema: JSONSchema = {
      type: 'array',
      items: {
        type: 'string',
      },
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(isListType(result.input.getType())).toBeTruthy();
    expect((result.input as ListComposer).ofType.getType()).toBe(GraphQLString);
    expect(isListType((result.output as ListComposer).getType())).toBeTruthy();
    expect((result.output as ListComposer).ofType.getType()).toBe(GraphQLString);
  });
  it('should return generic JSON type for array definitions with contains', async () => {
    const title = 'ExampleArray';
    const inputSchema: JSONSchema = {
      title,
      type: 'array',
      contains: {
        type: 'string',
      },
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input).toBe(result.output);
    const outputComposer = result.output as ListComposer;
    expect(isListType(outputComposer.getType())).toBeTruthy();
    expect(isScalarType(outputComposer.ofType.getType())).toBeTruthy();
    expect(outputComposer.ofType.getTypeName()).toBe(title);
  });
  it('should return union type inside a list type if array definition has items as an array', async () => {
    const title = 'FooOrBar';
    const inputSchema: JSONSchema = {
      title: 'ExampleObject',
      type: 'object',
      properties: {
        fooOrBar: {
          title,
          type: 'array',
          items: [
            {
              title: 'Foo',
              type: 'object',
              properties: {
                id: {
                  type: 'string',
                },
              },
            },
            {
              title: 'Bar',
              type: 'object',
              properties: {
                name: {
                  type: 'string',
                },
              },
            },
          ],
        },
      },
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(
      (result.output as ObjectTypeComposer).toSDL({
        deep: true,
      })
    ).toBe(
      /* GraphQL */ `
type ExampleObject {
  fooOrBar: [FooOrBar]
}

union FooOrBar = Foo | Bar

type Foo {
  id: String
}

${printType(GraphQLString)}

type Bar {
  name: String
}
`.trim()
    );
  });
  it('should create correct object types from object definition', async () => {
    const title = 'ExampleObject';
    const inputSchema: JSONSchema = {
      title,
      type: 'object',
      properties: {
        id: {
          type: 'string',
        },
      },
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect((result.input as InputTypeComposer).toSDL()).toBe(
      /* GraphQL */ `
input ExampleObject_Input {
  id: String
}
     `.trim()
    );
    expect((result.output as InputTypeComposer).toSDL()).toBe(
      /* GraphQL */ `
type ExampleObject {
  id: String
}
     `.trim()
    );
  });
  it('should create correct object types from object definition with additionalProperties', async () => {
    const title = 'ExampleObject';
    const inputSchema: JSONSchema = {
      title,
      type: 'object',
      properties: {
        id: {
          type: 'string',
        },
      },
      additionalProperties: {
        type: 'string',
      },
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect((result.input as InputTypeComposer).toSDL()).toContain(
      /* GraphQL */ `
scalar ExampleObject_Input
     `.trim()
    );
    expect((result.output as InputTypeComposer).toSDL()).toBe(
      /* GraphQL */ `
type ExampleObject {
  id: String
  additionalProperties: JSON
}
     `.trim()
    );
  });
  it('should return GraphQLSchema if object definition given with _schema title', async () => {
    const inputSchema: JSONSchema = {
      title: '_schema',
      type: 'object',
      properties: {
        query: {
          title: 'Query',
          type: 'object',
          properties: {
            foo: {
              type: 'string',
            },
          },
        },
      },
    };
    const { output } = await getComposerFromJSONSchema(inputSchema, logger);
    expect(output instanceof SchemaComposer).toBeTruthy();
    expect((output as SchemaComposer).toSDL()).toContain(
      /* GraphQL */ `
type Query {
  foo: String
}
     `.trim()
    );
  });
  it('should return Query type if object definition given with Query title', async () => {
    const inputSchema: JSONSchema = {
      title: 'Query',
      type: 'object',
      properties: {
        foo: {
          type: 'string',
        },
      },
    };
    const { output } = await getComposerFromJSONSchema(inputSchema, logger);
    expect(output instanceof ObjectTypeComposer).toBeTruthy();
    expect((output as SchemaComposer).toSDL()).toContain(
      /* GraphQL */ `
type Query {
  foo: String
}
     `.trim()
    );
  });
  it('should return Mutation type if object definition given with Query title', async () => {
    const inputSchema: JSONSchema = {
      title: 'Mutation',
      type: 'object',
      properties: {
        foo: {
          type: 'string',
        },
      },
    };
    const { output } = await getComposerFromJSONSchema(inputSchema, logger);
    expect(output instanceof ObjectTypeComposer).toBeTruthy();
    expect((output as SchemaComposer).toSDL()).toContain(
      /* GraphQL */ `
type Mutation {
  foo: String
}
     `.trim()
    );
  });
  it('should return Subscription type if object definition given with Subscription title', async () => {
    const inputSchema: JSONSchema = {
      title: 'Subscription',
      type: 'object',
      properties: {
        foo: {
          type: 'string',
        },
      },
    };
    const { output } = await getComposerFromJSONSchema(inputSchema, logger);
    expect(output instanceof ObjectTypeComposer).toBeTruthy();
    expect((output as SchemaComposer).toSDL()).toContain(
      /* GraphQL */ `
type Subscription {
  foo: String
}
     `.trim()
    );
  });
  it('should add arguments to Query fields with the object definition QueryTitle', async () => {
    const inputSchema: JSONSchema = {
      title: '_schema',
      type: 'object',
      properties: {
        query: {
          title: 'Query',
          type: 'object',
          properties: {
            foo: {
              type: 'string',
            },
          },
        },
        queryInput: {
          title: 'QueryInput',
          type: 'object',
          properties: {
            foo: {
              title: 'Foo',
              type: 'object',
              properties: {
                bar: {
                  type: 'string',
                },
              },
            },
          },
        },
      },
    };
    const { output } = await getComposerFromJSONSchema(inputSchema, logger);
    expect(output instanceof SchemaComposer).toBeTruthy();
    expect((output as SchemaComposer).toSDL()).toBe(
      /* GraphQL */ `
type Query {
  foo(input: Foo_Input): String
}

${printType(GraphQLString)}

input Foo_Input {
  bar: String
}
     `.trim()
    );
  });
  it('should choose correct type in union type generated from oneOf', async () => {
    const FooOrBar = {
      title: 'FooOrBar',
      oneOf: [
        {
          title: 'Foo',
          type: 'object',
          properties: {
            fooId: {
              type: 'string',
            },
          },
          required: ['fooId'],
        },
        {
          title: 'Bar',
          type: 'object',
          properties: {
            barId: {
              type: 'string',
            },
          },
        },
      ],
    };
    const inputSchema: JSONSchema = {
      title: '_schema',
      type: 'object',
      properties: {
        query: {
          title: 'Query',
          type: 'object',
          properties: {
            fooOrBarButFoo: FooOrBar,
            fooOrBarButBar: FooOrBar,
          },
        },
      },
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);

    const schemaComposer = result.output as SchemaComposer;
    const fooId = 'FOO_ID';
    const barId = 'BAR_ID';
    schemaComposer.addResolveMethods({
      Query: {
        fooOrBarButFoo: () => ({
          fooId: 'FOO_ID',
        }),
        fooOrBarButBar: () => ({
          barId: 'BAR_ID',
        }),
      },
    });
    const schema = schemaComposer.buildSchema();
    const executionResponse: any = await execute({
      schema,
      document: parse(/* GraphQL */ `
        fragment FooOrBarFragment on FooOrBar {
          __typename
          ... on Foo {
            fooId
          }
          ... on Bar {
            barId
          }
        }
        query TestQuery {
          fooOrBarButFoo {
            ...FooOrBarFragment
          }
          fooOrBarButBar {
            ...FooOrBarFragment
          }
        }
      `),
    });
    expect(executionResponse?.data?.fooOrBarButFoo?.__typename).toBe('Foo');
    expect(executionResponse?.data?.fooOrBarButFoo?.fooId).toBe(fooId);
    expect(executionResponse?.data?.fooOrBarButBar?.__typename).toBe('Bar');
    expect(executionResponse?.data?.fooOrBarButBar?.barId).toBe(barId);
  });
  it('should handle non-string enum values', async () => {
    const FooEnum = {
      title: 'FooEnum',
      type: 'string' as const,
      enum: [-1, 1],
    };
    const { output } = await getComposerFromJSONSchema(FooEnum, logger);
    expect(output instanceof EnumTypeComposer).toBeTruthy();
    const enumTypeComposer = output as EnumTypeComposer;
    const enumValuesMap = enumTypeComposer.getFields();
    expect(enumValuesMap).toMatchInlineSnapshot(`
      Object {
        "NEGATIVE_1": Object {
          "deprecationReason": undefined,
          "description": undefined,
          "directives": Array [],
          "extensions": Object {},
          "value": -1,
        },
        "_1": Object {
          "deprecationReason": undefined,
          "description": undefined,
          "directives": Array [],
          "extensions": Object {},
          "value": 1,
        },
      }
    `);
  });
  it('should handle strings with non-latin characters', async () => {
    const FooEnum = {
      title: 'FooEnum',
      type: 'string' as const,
      enum: ['לא', 'כן'],
    };

    const { output } = await getComposerFromJSONSchema(FooEnum, logger);
    expect(output instanceof EnumTypeComposer).toBeTruthy();
    const enumTypeComposer = output as EnumTypeComposer;
    const enumValuesMap = enumTypeComposer.getFields();
    expect(enumValuesMap).toMatchInlineSnapshot(`
      Object {
        "_1499__1503_": Object {
          "deprecationReason": undefined,
          "description": undefined,
          "directives": Array [],
          "extensions": Object {},
          "value": "כן",
        },
        "_1500__1488_": Object {
          "deprecationReason": undefined,
          "description": undefined,
          "directives": Array [],
          "extensions": Object {},
          "value": "לא",
        },
      }
    `);
  });
  it('should handle invalid property names', async () => {
    const jsonSchema: JSONSchemaObject = {
      type: 'object',
      title: '_schema',
      properties: {
        query: {
          type: 'object',
          title: 'Query',
          properties: {
            foo: {
              type: 'object',
              title: 'Foo',
              properties: {
                '0Bar': {
                  type: 'object',
                  title: 'Bar',
                  properties: {
                    barId: {
                      type: 'string',
                    },
                  },
                },
                '1Baz': {
                  type: 'object',
                  title: 'Baz',
                  properties: {
                    bazId: {
                      type: 'string',
                    },
                  },
                },
              },
            },
          },
        },
        queryInput: {
          type: 'object',
          title: 'QueryInput',
          properties: {
            foo: {
              type: 'object',
              title: 'Foo_Input',
              properties: {
                '0BarId': {
                  type: 'string',
                },
                '1BazId': {
                  type: 'string',
                },
              },
            },
          },
        },
      },
    };
    const { output } = await getComposerFromJSONSchema(jsonSchema, logger);
    expect(output instanceof SchemaComposer).toBeTruthy();
    const schema = (output as SchemaComposer).buildSchema();
    expect(printSchemaWithDirectives(schema)).toMatchInlineSnapshot(`
      "schema {
        query: Query
      }

      type Query {
        foo(input: Foo_Input_Input): Foo
      }

      type Foo {
        _0Bar: Bar
        _1Baz: Baz
      }

      type Bar {
        barId: String
      }

      type Baz {
        bazId: String
      }

      input Foo_Input_Input {
        _0BarId: String
        _1BazId: String
      }"
    `);
  });
  it('should workaround GraphQLjs falsy enum values bug', async () => {
    const values = [0, false, ''];
    const FooEnum = {
      title: 'FooEnum',
      type: ['number', 'boolean', 'string'] as any,
      enum: values,
    };
    const { output } = await getComposerFromJSONSchema(FooEnum, logger);
    expect(output instanceof EnumTypeComposer).toBeTruthy();
    const enumTypeComposer = output as EnumTypeComposer;
    const enumValuesMap = enumTypeComposer.getFields();
    Object.values(enumValuesMap).forEach((valueConfig, i) => {
      expect(valueConfig.value).toBe(values[i]?.toString());
    });
    expect.assertions(4);
  });
});
Example #28
Source File: schema.spec.ts    From Deep-Lynx with MIT License 4 votes vote down vote up
describe('The GraphQL Schema Generator', async () => {
    let containerID: string = process.env.TEST_CONTAINER_ID || '';
    let typeMappingID: string = '';
    let typeMapping: TypeMapping | undefined;
    let dataSourceID: string = '';
    let resultMetatypeRelationships: MetatypeRelationship[] = [];
    let data: DataStaging | undefined;
    let user: User;

    let maintenancePair: MetatypeRelationshipPair | undefined;
    let carDriverPair: MetatypeRelationshipPair | undefined;
    let driverLicensePair: MetatypeRelationshipPair | undefined;

    const car_metatype_keys: MetatypeKey[] = [
        new MetatypeKey({
            name: 'id',
            property_name: 'id',
            description: 'id of car',
            data_type: 'string',
            required: true,
        }),
        new MetatypeKey({
            name: 'name',
            property_name: 'name',
            description: 'name of car',
            data_type: 'string',
            required: true,
        }),
        new MetatypeKey({
            name: 'trim',
            property_name: 'trim',
            description: 'trim package',
            data_type: 'enumeration',
            options: ['le', 'lx'],
            required: true,
        }),
    ];

    const driver_metatype_keys: MetatypeKey[] = [
        new MetatypeKey({
            name: 'id',
            property_name: 'id',
            description: 'id of driver',
            data_type: 'string',
            required: true,
        }),
        new MetatypeKey({
            name: 'name',
            property_name: 'name',
            description: 'name of driver',
            data_type: 'string',
            required: true,
        }),
        new MetatypeKey({
            name: 'age',
            property_name: 'age',
            description: 'age of driver',
            data_type: 'string',
            required: true,
        }),
    ];

    const license_metatype_keys: MetatypeKey[] = [
        new MetatypeKey({
            name: 'id',
            property_name: 'id',
            description: 'license id',
            data_type: 'string',
            required: true,
        }),
        new MetatypeKey({
            name: 'type',
            property_name: 'type',
            description: 'type of license',
            data_type: 'enumeration',
            options: ['learners_permit', 'drivers_license'],
            required: true,
        }),
    ];

    const component_metatype_keys: MetatypeKey[] = [
        new MetatypeKey({
            name: 'id',
            property_name: 'id',
            description: 'id of car',
            data_type: 'number',
            required: true,
        }),
        new MetatypeKey({
            name: 'name',
            property_name: 'name',
            description: 'name of car',
            data_type: 'string',
            required: true,
        }),
    ];

    const manufacturer_metatype_keys: MetatypeKey[] = [
        new MetatypeKey({
            name: 'id',
            property_name: 'id',
            description: 'id of car',
            data_type: 'string',
            required: true,
        }),
        new MetatypeKey({
            name: 'name',
            property_name: 'name',
            description: 'name of car',
            data_type: 'string',
            required: true,
        }),
        new MetatypeKey({
            name: 'location',
            property_name: 'location',
            description: 'location of manufacturer',
            data_type: 'string',
            required: true,
        }),
    ];

    const tire_pressure_metatype_keys: MetatypeKey[] = [
        new MetatypeKey({
            name: 'id',
            property_name: 'id',
            description: 'id of car',
            data_type: 'string',
            required: true,
        }),
        new MetatypeKey({
            name: 'measurement',
            property_name: 'measurement',
            description: 'measurement',
            data_type: 'number',
            required: true,
        }),
        new MetatypeKey({
            name: 'measurement unit',
            property_name: 'measurement_unit',
            description: 'unit of measurement',
            data_type: 'string',
            required: true,
        }),
        new MetatypeKey({
            name: 'measurement name',
            property_name: 'measurement_name',
            description: 'name of measurement',
            data_type: 'string',
            required: true,
        }),
    ];

    const car_maintenance_metatype_keys: MetatypeKey[] = [
        new MetatypeKey({
            name: 'id',
            property_name: 'id',
            description: 'id of car',
            data_type: 'string',
            required: true,
        }),
        new MetatypeKey({
            name: 'name',
            property_name: 'name',
            description: 'name',
            data_type: 'string',
            required: true,
        }),
        new MetatypeKey({
            name: 'start date',
            property_name: 'start_date',
            description: 'start date',
            data_type: 'date',
            required: true,
        }),
        new MetatypeKey({
            name: 'average visits per year',
            property_name: 'average_visits',
            description: 'average visits per year',
            data_type: 'number',
            required: true,
        }),
    ];

    const maintenance_entry_metatype_keys: MetatypeKey[] = [
        new MetatypeKey({
            name: 'id',
            property_name: 'id',
            description: 'id',
            data_type: 'number',
            required: true,
        }),
        new MetatypeKey({
            name: 'check engine light flag',
            property_name: 'check_engine_light_flag',
            description: 'check engine light flag',
            data_type: 'boolean',
            required: true,
        }),
        new MetatypeKey({
            name: 'type',
            property_name: 'type',
            description: 'type',
            data_type: 'string',
            required: true,
        }),
    ];

    const partKeys: MetatypeKey[] = [
        new MetatypeKey({
            name: 'id',
            property_name: 'id',
            description: 'id of car',
            data_type: 'string',
            required: true,
        }),
        new MetatypeKey({
            name: 'name',
            property_name: 'name',
            description: 'name',
            data_type: 'string',
            required: true,
        }),
        new MetatypeKey({
            name: 'price',
            property_name: 'price',
            description: 'price',
            data_type: 'number',
            required: true,
        }),
        new MetatypeKey({
            name: 'quantity',
            property_name: 'quantity',
            description: 'quantity',
            data_type: 'number',
            required: true,
        }),
    ];

    const test_metatypes: Metatype[] = [
        new Metatype({
            name: 'Car',
            description: 'A Vehicle',
            keys: car_metatype_keys,
        }),
        new Metatype({
            name: 'Driver',
            description: 'Person driving Car',
            keys: driver_metatype_keys,
        }),
        new Metatype({
            name: 'License',
            description: 'License of Driver',
            keys: license_metatype_keys,
        }),
        new Metatype({
            name: 'Manufacturer',
            description: 'Creator of Car',
            keys: manufacturer_metatype_keys,
        }),
        new Metatype({
            name: 'Tire Pressure',
            description: 'Pressure of tire',
            keys: tire_pressure_metatype_keys,
        }),
        new Metatype({
            name: 'Maintenance',
            description: 'Maintenance records',
            keys: car_maintenance_metatype_keys,
        }),
        new Metatype({
            name: 'Maintenance Entry',
            description: 'Maintenance entries',
            keys: maintenance_entry_metatype_keys,
        }),
        new Metatype({
            name: 'Part',
            description: 'Physical part of car',
            keys: partKeys,
        }),
        new Metatype({
            name: 'Component',
            description: 'Base component of part',
            keys: component_metatype_keys,
        }),
    ];

    before(async function () {
        if (process.env.CORE_DB_CONNECTION_STRING === '') {
            Logger.debug('skipping export tests, no storage layer');
            this.skip();
        }

        await PostgresAdapter.Instance.init();
        const mapper = ContainerMapper.Instance;

        const container = await mapper.Create(
            'test suite',
            new Container({
                name: faker.name.findName(),
                description: faker.random.alphaNumeric(),
            }),
        );

        expect(container.isError).false;
        expect(container.value.id).not.null;
        containerID = container.value.id!;

        test_metatypes.forEach((metatype) => (metatype.container_id = containerID));

        const userResult = await UserMapper.Instance.Create(
            'test suite',
            new User({
                identity_provider_id: faker.random.uuid(),
                identity_provider: 'username_password',
                admin: false,
                display_name: faker.name.findName(),
                email: faker.internet.email(),
                roles: ['superuser'],
            }),
        );

        expect(userResult.isError).false;
        expect(userResult.value).not.empty;
        user = userResult.value;

        const relationshipMapper = MetatypeRelationshipMapper.Instance;

        const metatypeRepo = new MetatypeRepository();
        const created = await metatypeRepo.bulkSave(user, test_metatypes);
        expect(created.isError).false;

        const test_metatype_relationships: MetatypeRelationship[] = [
            new MetatypeRelationship({
                container_id: containerID,
                name: 'parent',
                description: 'item has parent',
            }),
            new MetatypeRelationship({
                container_id: containerID,
                name: 'uses',
                description: 'actor uses item',
            }),
            new MetatypeRelationship({
                container_id: containerID,
                name: 'has',
                description: 'item has item',
            }),
        ];

        // create the relationships
        const metatypeRelationships = await relationshipMapper.BulkCreate('test suite', test_metatype_relationships);
        expect(metatypeRelationships.isError).false;
        expect(metatypeRelationships.value).not.empty;
        resultMetatypeRelationships = metatypeRelationships.value;

        const test_metatype_relationship_pairs: MetatypeRelationshipPair[] = [
            new MetatypeRelationshipPair({
                name: 'owns',
                description: 'owns another entity',
                origin_metatype: test_metatypes.find((m) => m.name === 'Maintenance')!.id!,
                destination_metatype: test_metatypes.find((m) => m.name === 'Maintenance Entry')!.id!,
                relationship: resultMetatypeRelationships.find((m) => m.name === 'parent')!.id!,
                relationship_type: 'one:one',
                container_id: containerID,
            }),
            new MetatypeRelationshipPair({
                name: 'drives',
                description: 'actor drives item',
                origin_metatype: test_metatypes.find((m) => m.name === 'Driver')!.id!,
                destination_metatype: test_metatypes.find((m) => m.name === 'Car')!.id!,
                relationship: resultMetatypeRelationships.find((m) => m.name === 'uses')!.id!,
                relationship_type: 'one:many',
                container_id: containerID,
            }),
            new MetatypeRelationshipPair({
                name: 'holds',
                description: 'actor holds item',
                origin_metatype: test_metatypes.find((m) => m.name === 'Driver')!.id!,
                destination_metatype: test_metatypes.find((m) => m.name === 'License')!.id!,
                relationship: resultMetatypeRelationships.find((m) => m.name === 'uses')!.id!,
                relationship_type: 'one:one',
                container_id: containerID,
            }),
            new MetatypeRelationshipPair({
                name: 'has',
                description: 'item has item',
                origin_metatype: test_metatypes.find((m) => m.name === 'Car')!.id!,
                destination_metatype: test_metatypes.find((m) => m.name === 'Maintenance')!.id!,
                relationship: resultMetatypeRelationships.find((m) => m.name === 'has')!.id!,
                relationship_type: 'one:one',
                container_id: containerID,
            }),
        ];

        const pairs = await MetatypeRelationshipPairMapper.Instance.BulkCreate('test suite', test_metatype_relationship_pairs);
        expect(pairs.isError).false;
        expect(pairs.value).not.empty;
        maintenancePair = pairs.value[0];
        carDriverPair = pairs.value[1];
        driverLicensePair = pairs.value[2];

        const exp = await DataSourceMapper.Instance.Create(
            'test suite',
            new DataSourceRecord({
                container_id: containerID,
                name: 'Test Data Source',
                active: true,
                adapter_type: 'standard',
                data_format: 'json',
            }),
        );
        expect(exp.isError).false;
        expect(exp.value).not.empty;
        dataSourceID = exp.value.id!;

        const mapping = new TypeMapping({
            container_id: containerID,
            data_source_id: exp.value.id!,
            sample_payload: test_payload[0],
        });
        const saved = await new TypeMappingRepository().save(mapping, user);
        expect(saved.isError).false;
        typeMappingID = mapping.id!;
        typeMapping = mapping;

        // now import the data
        const newImport = await ImportMapper.Instance.CreateImport(
            'test suite',
            new Import({
                data_source_id: dataSourceID,
                reference: 'testing suite upload',
            }),
        );
        expect(newImport.isError).false;

        const inserted = await DataStagingMapper.Instance.Create(
            new DataStaging({
                data_source_id: dataSourceID,
                import_id: newImport.value.id!,
                data: test_payload[0],
                shape_hash: typeMapping.shape_hash,
            }),
        );
        expect(inserted.isError).false;
        expect(inserted.value.id).not.undefined;

        const stagingRepo = new DataStagingRepository();
        const insertedData = await stagingRepo.where().importID('eq', newImport.value.id).list({limit: 1});
        expect(insertedData.isError).false;
        expect(insertedData.value).not.empty;
        data = insertedData.value[0];

        const dataSource = new DataSourceFactory().fromDataSourceRecord(exp.value);

        // create the transformations and process
        const carMaintenanceKeys = test_metatypes.find((m) => m.name === 'Maintenance')!.keys;
        const carDriverKeys = test_metatypes.find((m) => m.name === 'Driver')!.keys;
        const driverLicenseKeys = test_metatypes.find((m) => m.name === 'License')!.keys;
        const entryKeys = test_metatypes.find((m) => m.name === 'Maintenance Entry')!.keys;
        // first generate all transformations for the type mapping, and set active
        const nodeTransformations: TypeTransformation[] = [
            new TypeTransformation({
                type: 'node',
                container_id: containerID,
                data_source_id: dataSource!.DataSourceRecord!.id!,
                type_mapping_id: typeMappingID,
                keys: [
                    new KeyMapping({
                        key: 'car_maintenance.id',
                        metatype_key_id: carMaintenanceKeys!.find((key) => key.name === 'id')?.id,
                    }),
                    new KeyMapping({
                        key: 'car_maintenance.name',
                        metatype_key_id: carMaintenanceKeys!.find((key) => key.name === 'name')?.id,
                    }),
                    new KeyMapping({
                        key: 'car_maintenance.start_date',
                        metatype_key_id: carMaintenanceKeys!.find((key) => key.name === 'start date')?.id,
                    }),
                    new KeyMapping({
                        key: 'car_maintenance.average_visits_per_year',
                        metatype_key_id: carMaintenanceKeys!.find((key) => key.name === 'average visits per year')?.id,
                    }),
                    new KeyMapping({
                        key: 'car_maintenance.visit_dates',
                        metatype_key_id: carMaintenanceKeys!.find((key) => key.name === 'visit dates')?.id,
                    }),
                ],
                metatype_id: test_metatypes.find((m) => m.name === 'Maintenance')!.id,
                unique_identifier_key: 'car_maintenance.id',
            }),
            new TypeTransformation({
                type: 'node',
                container_id: containerID,
                data_source_id: dataSource!.DataSourceRecord!.id!,
                type_mapping_id: typeMappingID,
                keys: [
                    new KeyMapping({
                        key: 'driver.id',
                        metatype_key_id: carDriverKeys!.find((key) => key.name === 'id')?.id,
                    }),
                    new KeyMapping({
                        key: 'driver.name',
                        metatype_key_id: carDriverKeys!.find((key) => key.name === 'name')?.id,
                    }),
                    new KeyMapping({
                        key: 'driver.age',
                        metatype_key_id: carDriverKeys!.find((key) => key.name === 'age')?.id,
                    }),
                ],
                metatype_id: test_metatypes.find((m) => m.name === 'Driver')!.id,
                unique_identifier_key: 'driver.id',
            }),
            new TypeTransformation({
                type: 'node',
                container_id: containerID,
                data_source_id: dataSource!.DataSourceRecord!.id!,
                type_mapping_id: typeMappingID,
                keys: [
                    new KeyMapping({
                        key: 'license.id',
                        metatype_key_id: driverLicenseKeys!.find((key) => key.name === 'id')?.id,
                    }),
                    new KeyMapping({
                        key: 'license.type',
                        metatype_key_id: driverLicenseKeys!.find((key) => key.name === 'type')?.id,
                    }),
                ],
                metatype_id: test_metatypes.find((m) => m.name === 'License')!.id,
                unique_identifier_key: 'license.id',
            }),
            new TypeTransformation({
                type: 'node',
                container_id: containerID,
                data_source_id: dataSource!.DataSourceRecord!.id!,
                type_mapping_id: typeMappingID,
                keys: [
                    new KeyMapping({
                        key: 'car_maintenance.maintenance_entries.[].id',
                        metatype_key_id: entryKeys!.find((key) => key.name === 'id')?.id,
                    }),
                    new KeyMapping({
                        key: 'car_maintenance.maintenance_entries.[].type',
                        metatype_key_id: entryKeys!.find((key) => key.name === 'type')?.id,
                    }),
                    new KeyMapping({
                        key: 'car_maintenance.maintenance_entries.[].check_engine_light_flag',
                        metatype_key_id: entryKeys!.find((key) => key.name === 'check engine light flag')?.id,
                    }),
                ],
                metatype_id: test_metatypes.find((m) => m.name === 'Maintenance Entry')?.id,
                unique_identifier_key: 'car_maintenance.maintenance_entries.[].id',
                root_array: 'car_maintenance.maintenance_entries',
            }),
        ];

        const nodeResult = await TypeTransformationMapper.Instance.BulkCreate('test suite', nodeTransformations);
        expect(nodeResult.isError).false;

        const edgeTransformations: TypeTransformation[] = [
            new TypeTransformation({
                type: 'edge',
                container_id: containerID,
                data_source_id: dataSource!.DataSourceRecord!.id!,
                type_mapping_id: typeMappingID,
                metatype_relationship_pair_id: maintenancePair!.id,
                origin_id_key: 'car_maintenance.id',
                origin_data_source_id: dataSource!.DataSourceRecord!.id!,
                origin_metatype_id: test_metatypes.find((m) => m.name === 'Maintenance')!.id,
                destination_id_key: 'car_maintenance.maintenance_entries.[].id',
                destination_data_source_id: dataSource!.DataSourceRecord!.id!,
                destination_metatype_id: test_metatypes.find((m) => m.name === 'Maintenance Entry')!.id,
                root_array: 'car_maintenance.maintenance_entries',
                keys: [],
            }),
            new TypeTransformation({
                type: 'edge',
                container_id: containerID,
                data_source_id: dataSource!.DataSourceRecord!.id!,
                type_mapping_id: typeMappingID,
                metatype_relationship_pair_id: carDriverPair!.id,
                origin_id_key: 'driver.id',
                origin_data_source_id: dataSource!.DataSourceRecord!.id!,
                origin_metatype_id: test_metatypes.find((m) => m.name === 'Driver')!.id,
                destination_id_key: 'car.id',
                destination_data_source_id: dataSource!.DataSourceRecord!.id!,
                destination_metatype_id: test_metatypes.find((m) => m.name === 'Car')!.id,
                keys: [],
            }),
            new TypeTransformation({
                type: 'edge',
                container_id: containerID,
                data_source_id: dataSource!.DataSourceRecord!.id!,
                type_mapping_id: typeMappingID,
                metatype_relationship_pair_id: driverLicensePair!.id,
                origin_id_key: 'driver.id',
                origin_data_source_id: dataSource!.DataSourceRecord!.id!,
                origin_metatype_id: test_metatypes.find((m) => m.name === 'Driver')!.id,
                destination_id_key: 'license.id',
                destination_data_source_id: dataSource!.DataSourceRecord!.id!,
                destination_metatype_id: test_metatypes.find((m) => m.name === 'License')!.id,
                keys: [],
            }),
        ];

        const edgeResult = await TypeTransformationMapper.Instance.BulkCreate('test suite', edgeTransformations);
        expect(edgeResult.isError).false;

        const active = await TypeMappingMapper.Instance.SetActive(typeMappingID);
        expect(active.isError).false;

        const dataStagingRepo = new DataStagingRepository();
        const records = await dataStagingRepo.where().dataSourceID('eq', dataSource!.DataSourceRecord!.id).list();
        expect(records.isError).false;
        expect(records.value.length).gt(0);

        for (const record of records.value) {
            const result = await ProcessData(record);
            expect(result.isError, result.error?.error).false;
        }

        return Promise.resolve();
    });

    after(async () => {
        await UserMapper.Instance.Delete(user.id!);
        await ContainerMapper.Instance.Delete(containerID);
        return PostgresAdapter.Instance.close();
    });

    it('can generate a valid schema', async () => {
        const schemaGenerator = new GraphQLSchemaGenerator();

        const containerSchema = await schemaGenerator.ForContainer(containerID, {});
        expect(containerSchema.isError).false;
        const typeMap = containerSchema.value.getTypeMap();

        // verify the types exist
        expect(typeMap['Garbage']).undefined;
        expect(typeMap['Car']).not.undefined;
        expect(typeMap['Driver']).not.undefined;
        expect(typeMap['License']).not.undefined;
        expect(typeMap['Manufacturer']).not.undefined;
        expect(typeMap['Tire_Pressure']).not.undefined;
        expect(typeMap['Maintenance']).not.undefined;
        expect(typeMap['Maintenance_Entry']).not.undefined;
        expect(typeMap['Part']).not.undefined;
        expect(typeMap['Component']).not.undefined;

        // check the fields now, make sure they're the right type
        expect((typeMap['Car'] as GraphQLObjectType).getFields()['id'].type).eq(GraphQLString);
        expect((typeMap['Car'] as GraphQLObjectType).getFields()['name'].type).eq(GraphQLString);
        expect((typeMap['Car'] as GraphQLObjectType).getFields()['trim'].type.toString()).eq('Car_trim_Enum_TypeA');
        // check the enum values
        const carVals = ((typeMap['Car'] as GraphQLObjectType).getFields()['trim'].type as GraphQLEnumType).getValues();
        expect(carVals.length).eq(2);
        expect(carVals[0].name).oneOf(['le', 'lx']);
        expect(carVals[1].name).oneOf(['le', 'lx']);

        expect((typeMap['Driver'] as GraphQLObjectType).getFields()['id'].type).eq(GraphQLString);
        expect((typeMap['Driver'] as GraphQLObjectType).getFields()['name'].type).eq(GraphQLString);
        expect((typeMap['Driver'] as GraphQLObjectType).getFields()['age'].type).eq(GraphQLString);

        expect((typeMap['License'] as GraphQLObjectType).getFields()['id'].type).eq(GraphQLString);
        expect((typeMap['License'] as GraphQLObjectType).getFields()['type'].type.toString()).eq('License_type_Enum_TypeA');
        // check the enum values
        const values = ((typeMap['License'] as GraphQLObjectType).getFields()['type'].type as GraphQLEnumType).getValues();
        expect(values.length).eq(2);
        expect(values[0].name).oneOf(['learners_permit', 'drivers_license']);
        expect(values[1].name).oneOf(['learners_permit', 'drivers_license']);

        expect((typeMap['Manufacturer'] as GraphQLObjectType).getFields()['id'].type).eq(GraphQLString);
        expect((typeMap['Manufacturer'] as GraphQLObjectType).getFields()['name'].type).eq(GraphQLString);
        expect((typeMap['Manufacturer'] as GraphQLObjectType).getFields()['location'].type).eq(GraphQLString);

        expect((typeMap['Tire_Pressure'] as GraphQLObjectType).getFields()['id'].type).eq(GraphQLString);
        expect((typeMap['Tire_Pressure'] as GraphQLObjectType).getFields()['measurement'].type).eq(GraphQLFloat);
        expect((typeMap['Tire_Pressure'] as GraphQLObjectType).getFields()['measurement_unit'].type).eq(GraphQLString);
        expect((typeMap['Tire_Pressure'] as GraphQLObjectType).getFields()['measurement_name'].type).eq(GraphQLString);

        expect((typeMap['Maintenance'] as GraphQLObjectType).getFields()['id'].type).eq(GraphQLString);
        expect((typeMap['Maintenance'] as GraphQLObjectType).getFields()['name'].type).eq(GraphQLString);
        expect((typeMap['Maintenance'] as GraphQLObjectType).getFields()['start_date'].type).eq(GraphQLString);
        expect((typeMap['Maintenance'] as GraphQLObjectType).getFields()['average_visits'].type).eq(GraphQLFloat);

        expect((typeMap['Maintenance_Entry'] as GraphQLObjectType).getFields()['id'].type).eq(GraphQLFloat);
        expect((typeMap['Maintenance_Entry'] as GraphQLObjectType).getFields()['check_engine_light_flag'].type).eq(GraphQLBoolean);
        expect((typeMap['Maintenance_Entry'] as GraphQLObjectType).getFields()['type'].type).eq(GraphQLString);

        expect((typeMap['Part'] as GraphQLObjectType).getFields()['id'].type).eq(GraphQLString);
        expect((typeMap['Part'] as GraphQLObjectType).getFields()['name'].type).eq(GraphQLString);
        expect((typeMap['Part'] as GraphQLObjectType).getFields()['price'].type).eq(GraphQLFloat);
        expect((typeMap['Part'] as GraphQLObjectType).getFields()['quantity'].type).eq(GraphQLFloat);
    });

    // the processed data should generate 1 Maintenance record and 2 Maintenance Entry records by this point
    it('can return nodes based on metadata', async () => {
        const schemaGenerator = new GraphQLSchemaGenerator();

        const containerSchema = await schemaGenerator.ForContainer(containerID, {});
        expect(containerSchema.isError).false;
    });
});
Example #29
Source File: node_schema.spec.ts    From Deep-Lynx with MIT License 4 votes vote down vote up
describe('A Node Schema Generator', async () => {
    let containerID: string = process.env.TEST_CONTAINER_ID || '';
    let transformationID: string = '';
    let nodeID: string = '';

    // this covers testing the hypertable creation and deletion as well
    before(async function () {
        if (process.env.CORE_DB_CONNECTION_STRING === '') {
            Logger.debug('skipping export tests, no storage layer');
            this.skip();
        }

        await PostgresAdapter.Instance.init();
        const mapper = ContainerStorage.Instance;
        const mMapper = MetatypeMapper.Instance;
        const keyStorage = MetatypeKeyMapper.Instance;
        const mappingStorage = TypeMappingMapper.Instance;

        const container = await mapper.Create(
            'test suite',
            new Container({
                name: faker.name.findName(),
                description: faker.random.alphaNumeric(),
            }),
        );

        expect(container.isError).false;
        expect(container.value.id).not.null;
        containerID = container.value.id!;

        const metatype = await mMapper.Create(
            'test suite',
            new Metatype({
                container_id: containerID,
                name: faker.name.findName(),
                description: faker.random.alphaNumeric(),
            }),
        );

        expect(metatype.isError).false;
        expect(metatype.value).not.empty;

        const testKeys = [...test_keys];
        testKeys.forEach((key) => (key.metatype_id = metatype.value.id!));

        const keys = await keyStorage.BulkCreate('test suite', testKeys);
        expect(keys.isError).false;

        const exp = await DataSourceMapper.Instance.Create(
            'test suite',
            new DataSourceRecord({
                container_id: containerID,
                name: 'Test Data Source',
                active: false,
                adapter_type: 'standard',
                data_format: 'json',
            }),
        );

        expect(exp.isError).false;
        expect(exp.value).not.empty;

        const mapping = await mappingStorage.CreateOrUpdate(
            'test suite',
            new TypeMapping({
                container_id: containerID,
                data_source_id: exp.value.id!,
                sample_payload: test_raw_payload,
            }),
        );

        const transformation = await TypeTransformationMapper.Instance.Create(
            'test suite',
            new TypeTransformation({
                name: 'Test',
                type_mapping_id: mapping.value.id!,
                type: 'timeseries',
                keys: [
                    new KeyMapping({
                        key: 'RADIUS',
                        column_name: 'radius',
                        value_type: 'float',
                    }),
                    new KeyMapping({
                        key: 'COLOR',
                        column_name: 'color',
                        value_type: 'string',
                    }),
                    new KeyMapping({
                        key: 'OPEN',
                        column_name: 'open',
                        value_type: 'boolean',
                    }),
                    new KeyMapping({
                        key: 'AT',
                        column_name: 'at',
                        value_type: 'date',
                        is_primary_timestamp: true,
                    }),
                ],
            }),
        );

        expect(transformation.isError).false;
        transformationID = transformation.value.id!;

        let created = await TypeTransformationMapper.Instance.CreateHypertable(transformation.value);
        expect(created.isError, created.error?.error).false;

        const mixed = new Node({
            container_id: containerID,
            metatype: metatype.value.id!,
            properties: {},
        });

        const node = await NodeMapper.Instance.CreateOrUpdateByCompositeID('test suite', mixed);
        expect(node.isError, metatype.error?.error).false;

        nodeID = node.value.id!;

        const nodeTransformation = await NodeMapper.Instance.AddTransformation(nodeID, transformationID);
        expect(nodeTransformation.isError).false;

        const entries = [
            new TimeseriesEntry({
                transformation_id: transformationID,
                data: [
                    new TimeseriesData({
                        column_name: 'radius',
                        value_type: 'float',
                        value: 1.2,
                    }),
                    new TimeseriesData({
                        column_name: 'color',
                        value_type: 'string',
                        value: 'green',
                    }),
                    new TimeseriesData({
                        column_name: 'open',
                        value_type: 'boolean',
                        value: false,
                    }),
                    new TimeseriesData({
                        column_name: 'at',
                        value_type: 'timestamp',
                        value: new Date(),
                    }),
                ],
            }),
            new TimeseriesEntry({
                transformation_id: transformationID,
                data: [
                    new TimeseriesData({
                        column_name: 'radius',
                        value_type: 'float',
                        value: 0.2,
                    }),
                    new TimeseriesData({
                        column_name: 'color',
                        value_type: 'string',
                        value: 'blue',
                    }),
                    new TimeseriesData({
                        column_name: 'open',
                        value_type: 'boolean',
                        value: false,
                    }),
                    new TimeseriesData({
                        column_name: 'at',
                        value_type: 'timestamp',
                        value: new Date(),
                    }),
                ],
            }),
        ];

        const repo = new TimeseriesEntryRepository();

        const saved = await repo.bulkSave(entries);

        expect(saved.isError, saved.error?.error).false;

        return Promise.resolve();
    });

    after(async () => {
        await TypeTransformationMapper.Instance.DeleteHypertable(transformationID);
        await ContainerMapper.Instance.Delete(containerID);
        // for some reason this suite of tests likes to not let go of the db, so this way we don't wait for it
        void PostgresAdapter.Instance.close();

        return Promise.resolve();
    });

    it('can generate the proper schema', async () => {
        const schemaGenerator = new GraphQLSchemaGenerator();

        const schema = await schemaGenerator.ForNode(nodeID, {});
        expect(schema.isError).false;

        const typeMap = schema.value.getTypeMap();
        expect(typeMap['Test']).not.undefined;

        expect((typeMap['Test'] as GraphQLObjectType).getFields()['radius'].type).eq(GraphQLFloat);
        expect((typeMap['Test'] as GraphQLObjectType).getFields()['open'].type).eq(GraphQLBoolean);
        expect((typeMap['Test'] as GraphQLObjectType).getFields()['color'].type).eq(GraphQLString);
        expect((typeMap['Test'] as GraphQLObjectType).getFields()['at'].type).eq(GraphQLString);

        return Promise.resolve();
    });

    it('can query data correctly', async () => {
        const schemaGenerator = new GraphQLSchemaGenerator();

        const schema = await schemaGenerator.ForNode(nodeID, {});
        expect(schema.isError).false;

        // double-check the schema
        const typeMap = schema.value.getTypeMap();
        expect(typeMap['Test']).not.undefined;

        expect((typeMap['Test'] as GraphQLObjectType).getFields()['radius'].type).eq(GraphQLFloat);
        expect((typeMap['Test'] as GraphQLObjectType).getFields()['open'].type).eq(GraphQLBoolean);
        expect((typeMap['Test'] as GraphQLObjectType).getFields()['color'].type).eq(GraphQLString);
        expect((typeMap['Test'] as GraphQLObjectType).getFields()['at'].type).eq(GraphQLString);

        //simple query
        try {
            let results = await graphql({
                schema: schema.value,
                source: simpleQuery,
            });

            if (results.errors) expect.fail(results.errors.join(','));
            expect(results.data?.Test.length).eq(2);
        } catch (e: any) {
            expect.fail(e);
        }

        //limit query
        try {
            let results = await graphql({
                schema: schema.value,
                source: limitQuery,
            });

            if (results.errors) expect.fail(results.errors.join(','));
            expect(results.data?.Test.length).eq(1);
        } catch (e: any) {
            expect.fail(e);
        }

        //color query
        try {
            let results = await graphql({
                schema: schema.value,
                source: colorQuery,
            });

            if (results.errors) expect.fail(results.errors.join(','));
            expect(results.data?.Test.length).eq(1);
        } catch (e: any) {
            expect.fail(e);
        }

        //color in query
        try {
            let results = await graphql({
                schema: schema.value,
                source: colorInQuery,
            });

            if (results.errors) expect.fail(results.errors.join(','));
            expect(results.data?.Test.length).eq(2);
        } catch (e: any) {
            expect.fail(e);
        }

        //time query
        try {
            let results = await graphql({
                schema: schema.value,
                source: timeQuery,
            });

            if (results.errors) expect.fail(results.errors.join(','));
            expect(results.data?.Test.length).eq(2);
        } catch (e: any) {
            expect.fail(e);
        }

        //reverse time query
        try {
            let results = await graphql({
                schema: schema.value,
                source: reverseTimeQuery,
            });

            if (results.errors) expect.fail(results.errors.join(','));
            expect(results.data?.Test.length).eq(0);
        } catch (e: any) {
            expect.fail(e);
        }

        return Promise.resolve();
    }).timeout(300000);

    it('can save data to file correctly', async () => {
        const schemaGenerator = new GraphQLSchemaGenerator();

        const schema = await schemaGenerator.ForNode(nodeID, {returnFile: true});
        expect(schema.isError).false;

        //simple query
        try {
            let results = await graphql({
                schema: schema.value,
                source: simpleFileQuery,
            });

            if (results.errors) expect.fail(results.errors.join(','));
            expect(results.data?.Test.file_size).gt(0);
        } catch (e: any) {
            expect.fail(e);
        }

        return Promise.resolve();
    });
});