graphql#GraphQLField TypeScript Examples

The following examples show how to use graphql#GraphQLField. 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: TriggerEventWriter.ts    From graphql-ts-client with MIT License 6 votes vote down vote up
constructor(
        private modelType: GraphQLObjectType | GraphQLInterfaceType,
        private idField: GraphQLField<any, any> | undefined,
        stream: WriteStream, 
        config: GeneratorConfig
    ) {
        super(stream, config);

        const simpleFieldNames = new Set<string>();
        const parameterizedFieldNames = new Set<string>();
        const fieldMap = modelType.getFields();
        for (const fieldName in fieldMap) {
            if (fieldName !== idField?.name) {
                if (fieldMap[fieldName].args.length === 0) {
                    simpleFieldNames.add(fieldName);
                } else {
                    parameterizedFieldNames.add(fieldName);
                }
            }
        }
        this.simpleFieldNames = simpleFieldNames;
        this.parameterizedFieldNames = parameterizedFieldNames;
    }
Example #2
Source File: types.ts    From tql with MIT License 6 votes vote down vote up
printField = (field: GraphQLField<any, any, any>): string => {
  const { args } = field;

  const isList = listType(field.type);
  const isNonNull = field.type instanceof GraphQLNonNull;
  const type = outputType(field.type);

  const printVariables = () => {
    return args.length > 0
      ? `(variables: { ${args.map(printVariable).join(", ")} })`
      : "";
  };

  if (type instanceof GraphQLScalarType) {
    return (
      `${args.length > 0 ? "" : "readonly"} ${field.name}${printVariables()}: ${
        isList ? `ReadonlyArray<${toPrimitive(type)}>` : `${toPrimitive(type)}`
      }` + (isNonNull ? "" : " | null")
    );
  } else if (type instanceof GraphQLEnumType) {
    return (
      `${args.length > 0 ? "" : "readonly"} ${field.name}${printVariables()}: ${
        isList ? `ReadonlyArray<${type.name}>` : `${type.name}`
      }` + (isNonNull ? "" : " | null")
    );
  } else if (
    type instanceof GraphQLInterfaceType ||
    type instanceof GraphQLUnionType ||
    type instanceof GraphQLObjectType
  ) {
    return (
      `${args.length > 0 ? "" : "readonly"} ${field.name}${printVariables()}: ${
        isList ? `ReadonlyArray<I${type.name}>` : `I${type.name}`
      }` + (isNonNull ? "" : " | null")
    );
  } else {
    throw new Error("Unable to print field.");
  }
}
Example #3
Source File: schema.ts    From squid with GNU General Public License v3.0 6 votes vote down vote up
function checkDerivedFrom(type: GraphQLNamedType, f: GraphQLField<any, any>): {field: string} | undefined {
    let directives = f.astNode?.directives?.filter(d => d.name.value == 'derivedFrom') || []
    if (directives.length == 0) return undefined
    if (!isEntityType(type)) throw new SchemaError(
        `@derivedFrom where applied to ${type.name}.${f.name}, but only entities can have lookup fields`
    )
    if (directives.length > 1) throw new SchemaError(
        `Multiple @derivedFrom where applied to ${type.name}.${f.name}`
    )
    let d = directives[0]
    let fieldArg = assertNotNull(d.arguments?.find(arg => arg.name.value == 'field'))
    assert(fieldArg.value.kind == 'StringValue')
    return {field: fieldArg.value.value}
}
Example #4
Source File: schema.ts    From squid with GNU General Public License v3.0 6 votes vote down vote up
function checkFieldIndex(type: GraphQLNamedType, f: GraphQLField<any, any>): Index | undefined {
    let unique = false
    let index = false

    f.astNode?.directives?.forEach(d => {
        if (d.name.value == 'unique') {
            assertCanBeIndexed(type, f)
            index = true
            unique = true
        } else if (d.name.value == 'index') {
            assertCanBeIndexed(type, f)
            let fieldsArg = d.arguments?.find(arg => arg.name.value == 'fields')
            if (fieldsArg) throw new SchemaError(
                `@index(fields: ...) where applied to ${type.name}.${f.name}, but fields argument is not allowed when @index is applied to a field`
            )
            let uniqueArg = d.arguments?.find(arg => arg.name.value == 'unique')
            if (uniqueArg) {
                assert(uniqueArg.value.kind == 'BooleanValue')
                unique = uniqueArg.value.value
            }
            index = true
        }
    })

    if (!index) return undefined

    return {
        fields: [{name: f.name}],
        unique
    }
}
Example #5
Source File: schema.ts    From squid with GNU General Public License v3.0 6 votes vote down vote up
function handleFulltextDirective(model: Model, object: GraphQLNamedType, f: GraphQLField<any, any>): void {
    f.astNode?.directives?.forEach(d => {
        if (d.name.value != 'fulltext') return
        if (!isEntityType(object) || !isStringField(f)) {
            throw new Error(`@fulltext directive can be only applied to String entity fields, but was applied to ${object.name}.${f.name}`)
        }
        let queryArgument = d.arguments?.find(arg => arg.name.value == 'query')
        assert(queryArgument != null)
        assert(queryArgument.value.kind == 'StringValue')
        let queryName = queryArgument.value.value
        let query = model[queryName]
        if (query == null) {
            query = model[queryName] = {
                kind: 'fts',
                sources: []
            }
        }
        assert(query.kind == 'fts')
        let src = query.sources.find(s => s.entity == object.name)
        if (src == null) {
            query.sources.push({
                entity: object.name,
                fields: [f.name]
            })
        } else {
            src.fields.push(f.name)
        }
    })
}
Example #6
Source File: upper-case.directive.ts    From nestjs-mercurius with MIT License 6 votes vote down vote up
visitFieldDefinition(
    field: GraphQLField<any, any>,
    _details: { objectType: GraphQLObjectType | GraphQLInterfaceType },
  ): GraphQLField<any, any> | void | null {
    const { resolve = defaultFieldResolver } = field;
    field.resolve = async function (...args) {
      const result = await resolve.apply(this, args);
      if (typeof result === 'string') {
        return result.toUpperCase();
      }
      return result;
    };
  }
Example #7
Source File: FetcherWriter.ts    From graphql-ts-client with MIT License 6 votes vote down vote up
private writePositivePropChangedDataType(field: GraphQLField<unknown, unknown>, withOptions: boolean, nullable: boolean) {
        const t = this.text.bind(this);
        t("{");
        if (!this.config.objectEditable) {
            t("readonly ");
        }
        if (withOptions) {
            t(`[key in XAlias]`);
        } else {
            t(`"${field.name}"`);
        }
        if (nullable) {
            t("?");
        }
        t(": ");
        this.typeRef(field.type, targetTypeOf(field.type) !== undefined ? "X" : undefined);
        t("}");
    }
Example #8
Source File: FetcherWriter.ts    From graphql-ts-client with MIT License 6 votes vote down vote up
private writeNegativeProp(field: GraphQLField<unknown, unknown>) {
        
        if (field.args.length !== 0 ||  targetTypeOf(field.type) !== undefined) {
            return;
        }

        const t = this.text.bind(this);

        t('\nreadonly "~');
        t(field.name);
        t('": ');
        t(this.fetcherTypeName);
        t("<Omit<T, '");
        t(field.name);
        t("'>, TVariables>;\n");
    }
Example #9
Source File: generateOperation.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
export default function generateOperation(
  operation: GraphQLField<any, any>,
  schema: GraphQLSchema,
  maxDepth: number = 3,
  options: GQLDocsGenOptions
): GQLTemplateGenericOp {
  const args: Array<GQLTemplateArgDeclaration> = getArgs(operation.args);
  const body: GQLTemplateOpBody = getBody(operation, schema, maxDepth, options);
  return {
    args,
    body,
  };
}
Example #10
Source File: getBody.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
export default function getBody(
  op: GraphQLField<any, any>,
  schema: GraphQLSchema,
  maxDepth: number = 3,
  options: GQLDocsGenOptions
): GQLTemplateOpBody {
  const args: Array<GQLTemplateArgInvocation> = op.args.map(arg => ({
    name: arg.name,
    value: `\$${arg.name}`,
  }));
  const fields: GQLTemplateField = getFields(op, schema, maxDepth, options);
  return {
    args,
    ...fields,
  };
}
Example #11
Source File: graphql.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
/**
 * Not exactly the same as the executor's definition of getFieldDef, in this
 * statically evaluated environment we do not always have an Object type,
 * and need to handle Interface and Union types.
 */
export function getFieldDef(
  schema: GraphQLSchema,
  parentType: GraphQLCompositeType,
  fieldAST: FieldNode
): GraphQLField<any, any> | undefined {
  const name = fieldAST.name.value;
  if (name === SchemaMetaFieldDef.name && schema.getQueryType() === parentType) {
    return SchemaMetaFieldDef;
  }
  if (name === TypeMetaFieldDef.name && schema.getQueryType() === parentType) {
    return TypeMetaFieldDef;
  }
  if (
    name === TypeNameMetaFieldDef.name &&
    (parentType instanceof GraphQLObjectType || parentType instanceof GraphQLInterfaceType || parentType instanceof GraphQLUnionType)
  ) {
    return TypeNameMetaFieldDef;
  }
  if (parentType instanceof GraphQLObjectType || parentType instanceof GraphQLInterfaceType) {
    return parentType.getFields()[name];
  }

  return undefined;
}
Example #12
Source File: FetcherWriter.ts    From graphql-ts-client with MIT License 5 votes vote down vote up
private writePositiveProp(field: GraphQLField<unknown, unknown>) {
        this.writePositivePropImpl(field, "SIMPLEST");
        this.writePositivePropImpl(field, "WITH_ARGS");
        this.writePositivePropImpl(field, "WITH_OTPIONS");
        this.writePositivePropImpl(field, "FULL");
    }
Example #13
Source File: selectors.ts    From tql with MIT License 5 votes vote down vote up
printSignature = (field: GraphQLField<any, any, any>): string => {
  const { name, args } = field;

  const type = outputType(field.type);

  const comments = [
    field.description && `@description ${field.description}`,
    field.deprecationReason && `@deprecated ${field.deprecationReason}`,
  ].filter(Boolean) as string[];

  const jsDocComment =
    comments.length > 0
      ? `
    /**
     ${comments.map((comment) => "* " + comment).join("\n")}
     */
    `
      : "";

  // @todo define Args type parameter as mapped type OR non-constant (i.e Array<Argument<...> | Argument<...>>)
  if (type instanceof GraphQLScalarType || type instanceof GraphQLEnumType) {
    return args.length > 0
      ? `${jsDocComment}\n readonly ${name}: <V extends { ${args
          .map(printVariable)
          .join(", ")} }>(variables: V) => Field<"${name}", [ ${args
          .map(printArgument)
          .join(", ")} ]>`
      : `${jsDocComment}\n readonly ${name}: () => Field<"${name}">`;
  } else {
    // @todo restrict allowed Field types
    return args.length > 0
      ? `
      ${jsDocComment}
      readonly ${name}: <V extends { ${args
          .map(printVariable)
          .join(", ")} }, T extends ReadonlyArray<Selection>>(
        variables: V,
        select: (t: I${type.toString()}Selector) => T
      ) => Field<"${name}", [ ${args
          .map(printArgument)
          .join(", ")} ], SelectionSet<T>>,
    `
      : `
      ${jsDocComment}
      readonly ${name}: <T extends ReadonlyArray<Selection>>(
        select: (t: I${type.toString()}Selector) => T
      ) => Field<"${name}", never, SelectionSet<T>>,
    `;
  }
}
Example #14
Source File: selectors.ts    From tql with MIT License 5 votes vote down vote up
printMethod = (field: GraphQLField<any, any, any>): string => {
  const { name, args } = field;

  const type = outputType(field.type);

  const comments = [
    field.description && `@description ${field.description}`,
    field.deprecationReason && `@deprecated ${field.deprecationReason}`,
  ].filter(Boolean);

  const jsDocComment =
    comments.length > 0
      ? `
  /**
   ${comments.map((comment) => "* " + comment).join("\n")}
    */
  `
      : "";

  if (type instanceof GraphQLScalarType || type instanceof GraphQLEnumType) {
    // @todo render arguments correctly
    return args.length > 0
      ? jsDocComment.concat(
          `${name}: (variables) => field("${name}", Object.entries(variables).map(([k, v]) => argument(k, v)) as any),`
        )
      : jsDocComment.concat(`${name}: () => field("${name}"),`);
  } else {
    const renderArgument = (arg: GraphQLArgument): string => {
      return `argument("${arg.name}", variables.${arg.name})`;
    };

    // @todo restrict allowed Field types
    return args.length > 0
      ? `
      ${jsDocComment}
      ${name}:(
        variables,
        select,
      ) => field("${name}", Object.entries(variables).map(([k, v]) => argument(k, v)) as any, selectionSet(select(${type.toString()}Selector))),
    `
      : `
      ${jsDocComment}
      ${name}: (
        select,
      ) => field("${name}", undefined as never, selectionSet(select(${type.toString()}Selector))),
    `;
  }
}
Example #15
Source File: schema.ts    From squid with GNU General Public License v3.0 5 votes vote down vote up
function canBeIndexed(f: GraphQLField<any, any>): boolean {
    let type = asNonNull(f)
    if (type instanceof GraphQLScalarType || type instanceof GraphQLEnumType) return true
    return isEntityType(type) && !f.astNode?.directives?.some(d => d.name.value == 'derivedFrom')
}
Example #16
Source File: schema.ts    From squid with GNU General Public License v3.0 5 votes vote down vote up
function assertCanBeIndexed(type: GraphQLNamedType, f: GraphQLField<any, any>): void {
    if (!isEntityType(type)) throw new SchemaError(
        `${type.name}.${f.name} can't be indexed, because ${type.name} is not an entity`
    )
    if (!canBeIndexed(f)) throw new SchemaError(
        `${type.name}.${f.name} can't be indexed, it is not a scalar, enum or foreign key`
    )
}
Example #17
Source File: schema.ts    From squid with GNU General Public License v3.0 5 votes vote down vote up
function asNonNull(f: GraphQLField<any, any>): GraphQLOutputType {
    let type = f.type
    if (type instanceof GraphQLNonNull) {
        type = type.ofType
    }
    return type
}
Example #18
Source File: schema.ts    From squid with GNU General Public License v3.0 5 votes vote down vote up
function asScalarField(f: GraphQLField<any, any>): GraphQLScalarType | undefined {
    let type = asNonNull(f)
    return type instanceof GraphQLScalarType ? type : undefined
}
Example #19
Source File: schema.ts    From squid with GNU General Public License v3.0 5 votes vote down vote up
function isStringField(f: GraphQLField<any, any>): boolean {
    return asScalarField(f)?.name == 'String'
}
Example #20
Source File: getFields.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
export default function getFields(
  field: GraphQLField<any, any>,
  schema: GraphQLSchema,
  depth: number = 2,
  options: GQLDocsGenOptions
): GQLTemplateField {
  const fieldType: GQLConcreteType = getType(field.type);
  const renderS3FieldFragment = options.useExternalFragmentForS3Object && isS3Object(fieldType);
  const subFields = !renderS3FieldFragment && (isObjectType(fieldType) || isInterfaceType(fieldType)) ? fieldType.getFields() : [];

  const subFragments: any = isInterfaceType(fieldType) || isUnionType(fieldType) ? schema.getPossibleTypes(fieldType) : {};

  if (depth < 1 && !(isScalarType(fieldType) || isEnumType(fieldType))) {
    return;
  }

  const fields: Array<GQLTemplateField> = Object.keys(subFields)
    .map(fieldName => {
      const subField = subFields[fieldName];
      return getFields(subField, schema, adjustDepth(subField, depth), options);
    })
    .filter(f => f);
  const fragments: Array<GQLTemplateFragment> = Object.keys(subFragments)
    .map(fragment => getFragment(subFragments[fragment], schema, depth, fields, null, false, options))
    .filter(f => f);

  // Special treatment for S3 input
  // Swift SDK needs S3 Object to have fragment
  if (renderS3FieldFragment) {
    fragments.push(getFragment(fieldType as GraphQLObjectType, schema, depth, [], 'S3Object', true, options));
  }

  // if the current field is an object and none of the subfields are included, don't include the field itself
  if (!(isScalarType(fieldType) || isEnumType(fieldType)) && fields.length === 0 && fragments.length === 0 && !renderS3FieldFragment) {
    return;
  }

  return {
    name: field.name,
    fields,
    fragments,
    hasBody: !!(fields.length || fragments.length),
  };
}
Example #21
Source File: objectType.ts    From genql with MIT License 5 votes vote down vote up
objectType = (
    type: GraphQLObjectType | GraphQLInterfaceType | GraphQLInputObjectType,
    ctx: RenderContext,
) => {
    const typeObj: FieldMap<string> = Object.keys(type.getFields()).reduce<
        FieldMap<string>
    >((r, f) => {
        const field = type.getFields()[f]
        const namedType = getNamedType(field.type)
        const fieldObj: Field<string> = { type: namedType.name }
        r[f] = fieldObj

        const args: GraphQLArgument[] =
            (<GraphQLField<any, any>>field).args || []

        if (args.length > 0) {
            fieldObj.args = args.reduce<ArgMap<string>>((r, a) => {
                const concreteType = a.type.toString()
                const typename = getNamedType(a.type).name
                r[a.name] = [typename]
                if (typename !== concreteType) {
                    r[a.name]?.push(concreteType)
                }
                return r
            }, {})
        }

        return r
    }, {})

    if (isInterfaceType(type) && ctx.schema) {
        ctx.schema.getPossibleTypes(type).map((t) => {
            if (!isEmpty(typeObj)) {
                typeObj[`on_${t.name}`] = { type: t.name }
            }
        })
    }

    if (!isEmpty(typeObj)) {
        typeObj.__typename = { type: 'String' }
    }

    // const scalar = Object.keys(type.getFields())
    //   .map(f => type.getFields()[f])
    //   .filter(f => isScalarType(getNamedType(f.type)) || isEnumType(getNamedType(f.type)))
    //   .map(f => f.name)

    // if (scalar.length > 0) typeObj.scalar = scalar

    return typeObj
}
Example #22
Source File: toArgsString.ts    From genql with MIT License 5 votes vote down vote up
toArgsString = (field: GraphQLField<any, any, any>) => {
  return `{${field.args.map(a => `${argumentComment(a)}${a.name}${renderTyping(a.type, false, true)}`).join(',')}}`
}
Example #23
Source File: comment.ts    From genql with MIT License 5 votes vote down vote up
fieldComment = (field: GraphQLEnumValue | GraphQLField<any, any, any>) =>
  comment({
    deprecated: field.deprecationReason,
    text: field.description,
  })
Example #24
Source File: Writer.ts    From graphql-ts-client with MIT License 5 votes vote down vote up
protected typeRef(
        type: GraphQLType,
        objectRender?: string | ((type: GraphQLObjectType | GraphQLInterfaceType, field: GraphQLField<any, any>) => boolean)
    ) {
        if (type instanceof GraphQLScalarType) {
            const mappedType = 
                (this.config.scalarTypeMap ?? EMPTY_MAP)[type.name] 
                ?? SCALAR_MAP[type.name];
            if (mappedType === undefined) {
                throw new Error(`Unknown scalar type ${type.name}`);
            }
            if (typeof mappedType === 'string') {
                this.text(mappedType);
            } else {
                this.text(mappedType.typeName);
            }
        } else if (type instanceof GraphQLObjectType || 
            type instanceof GraphQLInterfaceType ||
            type instanceof GraphQLUnionType
        ) {
            if (typeof objectRender === "string") {
                this.text(objectRender);
            } else if (type instanceof GraphQLUnionType) {
                this.enter("BLANK");
                for (const itemType of type.getTypes()) {
                    this.separator(" | ");
                    this.text(itemType.name);    
                }
                this.leave();
            } else if (typeof objectRender === 'function') {
                this.scope({type: "BLOCK", multiLines: true}, () => {
                    const fieldMap = type.getFields();
                    for (const fieldName in fieldMap) {
                        const field = fieldMap[fieldName];
                        if (objectRender(type, field)) {
                            this.separator(", ");
                            this.text("readonly ");
                            this.text(fieldName);
                            this.text(": ");
                            this.typeRef(field.type, objectRender);
                        }
                    }
                });
            } else {
                this.text(type.name);
            }
        } else if (type instanceof GraphQLEnumType || type instanceof GraphQLInputObjectType) {
            this.text(type.name);
        } else if (type instanceof GraphQLNonNull) {
            this.typeRef(type.ofType, objectRender);
        } else if (type instanceof GraphQLList) {
            if (type.ofType instanceof GraphQLNonNull) {
                if (!this.config.arrayEditable) {
                    this.text("readonly ");
                }
                this.typeRef(type.ofType, objectRender);
                this.text("[]");
            } else {
                if (!this.config.arrayEditable) {
                    this.text("Readonly");
                }
                this.text("Array<");
                this.typeRef(type.ofType, objectRender);
                this.text(" | undefined>");
            }
        }
    }
Example #25
Source File: Writer.ts    From graphql-ts-client with MIT License 5 votes vote down vote up
protected importFieldTypes(field: GraphQLField<unknown, unknown>) {
        this.importType(field.type);
        for (const arg of field.args) {
            this.importType(arg.type);
        }
    }
Example #26
Source File: Writer.d.ts    From graphql-ts-client with MIT License 5 votes vote down vote up
protected importFieldTypes(field: GraphQLField<unknown, unknown>): void;
Example #27
Source File: Writer.d.ts    From graphql-ts-client with MIT License 5 votes vote down vote up
protected typeRef(type: GraphQLType, objectRender?: string | ((type: GraphQLObjectType | GraphQLInterfaceType, field: GraphQLField<any, any>) => boolean)): void;
Example #28
Source File: TriggerEventWriter.d.ts    From graphql-ts-client with MIT License 5 votes vote down vote up
constructor(modelType: GraphQLObjectType | GraphQLInterfaceType, idField: GraphQLField<any, any> | undefined, stream: WriteStream, config: GeneratorConfig);
Example #29
Source File: schema.ts    From squid with GNU General Public License v3.0 4 votes vote down vote up
function addEntityOrJsonObjectOrInterface(model: Model, type: GraphQLObjectType | GraphQLInterfaceType): void {
    if (model[type.name]) return

    let kind: 'entity' | 'object' | 'interface' = isEntityType(type)
        ? 'entity'
        :  type instanceof GraphQLInterfaceType ? 'interface' : 'object'

    let properties: Record<string, Prop> = {}
    let interfaces: string[] = []
    let indexes: Index[] = type instanceof GraphQLObjectType ? checkEntityIndexes(type) : []
    let description = type.description || undefined

    switch(kind) {
        case 'entity':
            model[type.name] = {kind, properties, description, interfaces, indexes}
            break
        case 'object':
            model[type.name] = {kind, properties, description, interfaces}
            break
        case 'interface':
            model[type.name] = {kind, properties, description}
            break
        default:
            throw unexpectedCase(kind)
    }

    let fields = type.getFields()
    if (kind == 'entity') {
        if (fields.id == null) {
            properties.id = {
                type: {kind: 'scalar', name: 'ID'},
                nullable: false
            }
        } else {
            let correctIdType = fields.id.type instanceof GraphQLNonNull
                && fields.id.type.ofType instanceof GraphQLScalarType
                && fields.id.type.ofType.name === 'ID'
            if (!correctIdType) {
                throw unsupportedFieldTypeError(type.name + '.id')
            }
        }
    }

    for (let key in fields) {
        let f: GraphQLField<any, any> = fields[key]

        handleFulltextDirective(model, type, f)

        let propName = `${type.name}.${f.name}`
        let fieldType = f.type
        let nullable = true
        let description = f.description || undefined
        let derivedFrom = checkDerivedFrom(type, f)
        let index = checkFieldIndex(type, f)
        let unique = index?.unique || false

        if (index) {
            indexes.push(index)
        }

        if (fieldType instanceof GraphQLNonNull) {
            nullable = false
            fieldType = fieldType.ofType
        }

        let list = unwrapList(fieldType)
        fieldType = list.item

        if (fieldType instanceof GraphQLScalarType) {
            properties[key] = {
                type: wrapWithList(list.nulls, {
                    kind: 'scalar',
                    name: fieldType.name
                }),
                nullable,
                description
            }
        } else if (fieldType instanceof GraphQLEnumType) {
            addEnum(model, fieldType)
            properties[key] = {
                type: wrapWithList(list.nulls, {
                    kind: 'enum',
                    name: fieldType.name
                }),
                nullable,
                description
            }
        } else if (fieldType instanceof GraphQLUnionType) {
            addUnion(model, fieldType)
            properties[key] = {
                type: wrapWithList(list.nulls, {
                    kind: 'union',
                    name: fieldType.name
                }),
                nullable,
                description
            }
        } else if (fieldType instanceof GraphQLObjectType) {
            if (isEntityType(fieldType)) {
                switch(list.nulls.length) {
                    case 0:
                        if (derivedFrom) {
                            if (!nullable) {
                                throw new SchemaError(`Property ${propName} must be nullable`)
                            }
                            properties[key] = {
                                type: {
                                    kind: 'lookup',
                                    entity: fieldType.name,
                                    field: derivedFrom.field
                                },
                                nullable,
                                description
                            }
                        } else {
                            if (unique && nullable) {
                                throw new SchemaError(`Unique property ${propName} must be non-nullable`)
                            }
                            properties[key] = {
                                type: {
                                    kind: 'fk',
                                    foreignEntity: fieldType.name
                                },
                                nullable,
                                unique,
                                description
                            }
                        }
                        break
                    case 1:
                        if (derivedFrom == null) {
                            throw new SchemaError(`@derivedFrom directive is required on ${propName} declaration`)
                        }
                        properties[key] = {
                            type: {
                                kind: 'list-lookup',
                                entity: fieldType.name,
                                field: derivedFrom.field
                            },
                            nullable: false,
                            description
                        }
                        break
                    default:
                        throw unsupportedFieldTypeError(propName)
                }
            } else {
                addEntityOrJsonObjectOrInterface(model, fieldType)
                properties[key] = {
                    type: wrapWithList(list.nulls, {
                        kind: 'object',
                        name: fieldType.name
                    }),
                    nullable,
                    description
                }
            }
        } else {
            throw unsupportedFieldTypeError(propName)
        }
    }

    if (kind != 'interface') {
        type.getInterfaces().forEach(i => {
            addEntityOrJsonObjectOrInterface(model, i)
            interfaces.push(i.name)
        })
    }
}