graphql#GraphQLInterfaceType TypeScript Examples

The following examples show how to use graphql#GraphQLInterfaceType. 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: GraphQLStateFetcherWriter.ts    From graphql-ts-client with MIT License 6 votes vote down vote up
protected importedNamesForSuperType(superType: GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType): string[] {
        if (!this.ctx.triggerableTypes.has(superType)) {
            return super.importedNamesForSuperType(superType);
        }
        return [
            ...super.importedNamesForSuperType(superType),
            `${superType.name}FlatType`
        ];
    }
Example #2
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 #3
Source File: GraphQLStateGenerator.ts    From graphql-ts-client with MIT License 6 votes vote down vote up
protected additionalExportedTypeNamesForFetcher(
        modelType: GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType,
        ctx: FetcherContext
    ): ReadonlyArray<string> {
        if (ctx.triggerableTypes.has(modelType)) {
            return [
                ...super.additionalExportedTypeNamesForFetcher(modelType, ctx),
                `${modelType.name}FlatType`
            ];
        }
        return super.additionalExportedTypeNamesForFetcher(modelType, ctx);
    }
Example #4
Source File: interfaceType.ts    From genql with MIT License 6 votes vote down vote up
interfaceType = (
    type: GraphQLInterfaceType,
    ctx: RenderContext,
) => {
    if (!ctx.schema) {
        throw new Error('schema is required to render unionType')
    }
    const typeNames = ctx.schema.getPossibleTypes(type).map((t) => t.name)
    if (!typeNames.length) {
        objectType(type, ctx)
    } else {
        ctx.addCodeBlock(
            `${typeComment(type)}export type ${type.name} = (${typeNames.join(
                ' | ',
            )}) & { __isUnion?: true }`,
        )
    }
}
Example #5
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 #6
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 #7
Source File: schema-resolver.ts    From graphql-mesh with MIT License 6 votes vote down vote up
private createObjectTypeConfig(soapType: SoapObjectType): GraphQLObjectTypeConfig<any, any> {
    const fields = (): GraphQLFieldConfigMap<any, any> => {
      const fieldMap: GraphQLFieldConfigMap<any, any> = {};
      this.appendObjectTypeFields(fieldMap, soapType);
      return fieldMap;
    };

    const interfaces = (): GraphQLInterfaceType[] => {
      const interfaces: GraphQLInterfaceType[] = [];
      this.appendInterfaces(interfaces, soapType);
      return interfaces;
    };

    return {
      name: this.options.outputNameResolver(soapType),
      fields,
      interfaces,
    };
  }
Example #8
Source File: serializeToJSON.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
function serializeInterfaceType(type: GraphQLInterfaceType) {
  const { name, description } = type;
  const fields = Object.values(type.getFields());

  return {
    kind: 'InterfaceType',
    name,
    description,
    fields: fields.map(field => ({
      name: field.name,
      type: String(field.type),
      description: field.description,
    })),
  };
}
Example #9
Source File: schema-resolver.ts    From graphql-mesh with MIT License 6 votes vote down vote up
private resolveInterfaceType(soapType: SoapObjectType): GraphQLInterfaceType {
    if (this.alreadyResolvedInterfaceTypes.has(soapType)) {
      return this.alreadyResolvedInterfaceTypes.get(soapType);
    }

    const interfaceType: GraphQLInterfaceType = this.createInterfaceType(soapType);
    this.alreadyResolvedInterfaceTypes.set(soapType, interfaceType);
    return interfaceType;
  }
Example #10
Source File: GeneratorConfig.ts    From graphql-ts-client with MIT License 5 votes vote down vote up
export function validateConfigAndSchema(
    config: GeneratorConfig,
    schema: GraphQLSchema
) {
    const typeMap = schema.getTypeMap();
    for (const typeName in typeMap) {
        const type = typeMap[typeName]!;
        if (type instanceof GraphQLObjectType || type instanceof GraphQLInterfaceType) {
            const fieldMap = type.getFields();
            for (const fieldName in fieldMap) {
                const field = fieldMap[fieldName]!;
                if (BUILT_IN_FEILDS.has(field.name)) {
                    throw new Error(
                        `Illegal field '${field.name}' of type '${type.name}', ` +
                        "it's name is protected by 'graphql-ts-client-api', please change the server-side app"
                    );
                }
            }
        }
    }

    const idFieldMap = config.idFieldMap;
    if (idFieldMap !== undefined) {
        for (const typeName in idFieldMap) {
            const type = typeMap[typeName];
            if (!(type instanceof GraphQLObjectType) && !(type instanceof GraphQLInterfaceType)) {
                throw new Error(
                    `config.idFieldMap contains an illegal key '${typeName}', ` +
                    "that is neither a graphql object type nor graphql interface type"
                );
            }
            const fieldMap = type.getFields();
            const idField = fieldMap[idFieldMap[typeName]];
            if (idField === undefined) {
                throw new Error(
                    `config.idFieldMap['${typeName}'] is illegal, ` +
                    `there is not field named '${idFieldMap[typeName]}' in the type '${typeName}'`
                );
            }
            if (targetTypeOf(idField.type) !== undefined) {
                throw new Error(
                    `config.idFieldMap['${typeName}'] is illegal, ` +
                    `the field '${idFieldMap[typeName]}' of the type '${typeName}' is not scalar`
                );
            }
        }
    }
    
    const excludeMap = config.defaultFetcherExcludeMap;
    if (excludeMap !== undefined) {
        for (const typeName in excludeMap) {
            const type = typeMap[typeName];
            if (!(type instanceof GraphQLObjectType) && !(type instanceof GraphQLInterfaceType)) {
                throw new Error(
                    `config.defaultFetcherExcludeMap contains an illegal key '${typeName}' ` +
                    "that is neither a graphql object type nor graphql interface type"
                );
            }
            const fieldMap = type.getFields();
            const fieldNames = excludeMap[typeName];
            if (!Array.isArray(fieldNames)) {
                throw new Error(`config.defaultFetcherExcludeMap['${typeName}'] is not array`);
            }
            for (let i = fieldNames.length - 1; i >= 0; i--) {
                const fieldName = fieldNames[i];
                if (typeof fieldName !== 'string') {
                    throw new Error(`config.defaultFetcherExcludeMap['${typeName}'][${i}] is not string`);
                }
                if (fieldMap[fieldName] === undefined) {
                    throw new Error(
                        `config.defaultFetcherExcludeMap['${typeName}'][${i}] is illegal, ` +
                        `its value '${fieldName}' is not a field of graphql type '${typeName}'`
                    );
                }
            }
        }
    }
}
Example #11
Source File: schema-resolver.ts    From graphql-mesh with MIT License 5 votes vote down vote up
private alreadyResolvedInterfaceTypes: Map<SoapType, GraphQLInterfaceType> = new Map();
Example #12
Source File: TypedConfigurationWriter.ts    From graphql-ts-client with MIT License 5 votes vote down vote up
private writeFetcherType(fetcherType: GraphQLObjectType | GraphQLInterfaceType) {

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

        this.scope({type: "BLOCK", multiLines: true, suffix: ";\n"}, () => {
            
            if (fetcherType.name !== 'Query') {

                const idField = this.ctx.idFieldMap.get(fetcherType);
                if (idField !== undefined) {
                    t(`readonly " $id": `);
                    this.typeRef(idField.type);
                    t(";\n");
                }

                t(`readonly " $evictEvent": ${fetcherType.name}EvictEvent;\n`);
                t(`readonly " $changeEvent": ${fetcherType.name}ChangeEvent;\n`);
            }

            const fieldMap = fetcherType.getFields();
            const associationTypeMap = this.associationTypeMapOf(fetcherType);

            t(`readonly " $associationTypes": `);
            this.scope({type: "BLOCK", multiLines: true, suffix: ";\n"}, () => {
                for (const [fieldName, typeName] of associationTypeMap) {
                    this.separator(", ");
                    t(`readonly ${fieldName}: "${typeName}"`);
                }
            });

            t(`readonly " $associationArgs": `);
            this.scope({type: "BLOCK", multiLines: true, suffix: ";\n"}, () => {
                for (const fieldName in fieldMap) {
                    if (fieldMap[fieldName].args.length !== 0) {
                        this.separator(", ");
                        t(`readonly ${fieldName}: ${fetcherType.name}Args["${fieldName}"]`);
                    }
                }
            });

            t(`readonly " $associationTargetTypes": `);
            this.scope({type: "BLOCK", multiLines: true, suffix: ";\n"}, () => {
                const triggerableTypeNames = new Set<string>(
                    Array
                    .from(this.ctx.triggerableTypes)
                    .map(it => (it as GraphQLObjectType | GraphQLInterfaceType).name)
                );
                for (const [fieldName, typeName] of associationTypeMap) {
                    if (triggerableTypeNames.has(typeName)) {
                        this.separator(", ");
                        t(`readonly ${fieldName}: ${typeName}FlatType`);
                    }
                }
            });
        })
    }
Example #13
Source File: objectType.ts    From genql with MIT License 5 votes vote down vote up
objectType = (
    type: GraphQLObjectType | GraphQLInterfaceType,
    ctx: RenderContext,
    wrapper: 'Promise' | 'Observable',
) => {
    // console.log(Object.keys(type.getFields()))
    const fieldsMap: GraphQLFieldMap<any, any> = ctx.config?.sortProperties
        ? sortKeys(type.getFields())
        : type.getFields()

    const fieldStrings = Object.keys(fieldsMap).map((fieldName) => {
        const field = fieldsMap[fieldName]
        const resolvedType = getNamedType(field.type)
        // leaf type, obly has.get() method
        const stopChain =
            isListType(field.type) ||
            (isNonNullType(field.type) && isListType(field.type.ofType)) ||
            isUnionType(resolvedType)
        // non leaf type, has .get method
        const resolvable = !(
            isEnumType(resolvedType) || isScalarType(resolvedType)
        )
        const argsPresent = field.args.length > 0
        const argsOptional = !field.args.find((a) => isNonNullType(a.type))
        const argsString = toArgsString(field)

        const executeReturnType = renderTyping(field.type, true, false, false)
        const executeReturnTypeWithTypeMap = renderTyping(
            field.type,
            true,
            false,
            false,
            (x: string) => `FieldsSelection<${x}, R>`,
        )

        //     get: <R extends RepositoryRequest>(
        //         request: R,
        //         defaultValue?: Repository,
        //     ) => Promise<FieldsSelection<Repository, R>>
        // }

        const getFnType = `{get: <R extends ${requestTypeName(
            resolvedType,
        )}>(request: R, defaultValue?: ${executeReturnTypeWithTypeMap}) => ${wrapper}<${executeReturnTypeWithTypeMap}>}`
        const fieldType = resolvable
            ? stopChain
                ? getFnType
                : `${chainTypeName(resolvedType, wrapper)} & ${getFnType}`
            : `{get: (request?: boolean|number, defaultValue?: ${executeReturnType}) => ${wrapper}<${executeReturnType}>}`

        const result: string[] = []

        if (argsPresent) {
            result.push(
                `((args${
                    argsOptional ? '?' : ''
                }: ${argsString}) => ${fieldType})`,
            )
        }

        if (!argsPresent || argsOptional) {
            result.push(`(${fieldType})`)
        }

        return `${fieldComment(field)}${field.name}: ${result.join('&')}`
    })

    ctx.addImport(RUNTIME_LIB_NAME, false, 'FieldsSelection', true, true)

    if (wrapper === 'Observable') {
        ctx.addImport(RUNTIME_LIB_NAME, false, 'Observable', true, true)
    }

    ctx.addCodeBlock(
        `${typeComment(type)}export interface ${chainTypeName(
            type,
            wrapper,
        )}{\n    ${fieldStrings.join(',\n    ')}\n}`,
    )
}
Example #14
Source File: Generator.ts    From graphql-ts-client with MIT License 5 votes vote down vote up
private async generateFetcherTypes(ctx: FetcherContext) {
        const dir = join(this.config.targetDir, "fetchers");
        const emptyFetcherNameMap = new Map<GraphQLType, string>();
        const defaultFetcherNameMap = new Map<GraphQLType, string>();
        const promises = ctx.fetcherTypes
            .map(async type => {
                const stream = createStreamAndLog(
                    join(dir, `${type.name}${this.config?.fetcherSuffix ?? "Fetcher"}.ts`)
                );
                const writer = this.createFetcheWriter(
                    type, 
                    ctx,
                    stream, 
                    this.config
                );
                emptyFetcherNameMap.set(type, writer.emptyFetcherName);
                if (writer.defaultFetcherName !== undefined) {
                    defaultFetcherNameMap.set(type, writer.defaultFetcherName);
                }
                writer.write();
                await closeStream(stream);
            });
        
        await Promise.all([
            ...promises,
            (async() => {
                const stream = createStreamAndLog(join(dir, "index.ts"));
                for (const type of ctx.fetcherTypes) {
                    const fetcherTypeName = `${type.name}${this.config?.fetcherSuffix ?? "Fetcher"}`;
                    stream.write(
                        `export type {${
                            [
                                fetcherTypeName,
                                (type instanceof GraphQLObjectType || type instanceof GraphQLInterfaceType) &&
                                ctx.typesWithParameterizedField.has(type) ? 
                                `${type.name}Args` : 
                                undefined,
                                ...this.additionalExportedTypeNamesForFetcher(type, ctx)
                            ]
                            .filter(text => text !== undefined)
                            .join(", ")
                        }} from './${fetcherTypeName}';\n`
                    );
                    const defaultFetcherName = defaultFetcherNameMap.get(type);
                    stream.write(
                        `export {${
                            emptyFetcherNameMap.get(type)
                        }${
                            defaultFetcherName !== undefined ?
                            `, ${defaultFetcherName}` :
                            ''
                        }} from './${fetcherTypeName}';\n`
                    );
                }
                await stream.end();
            })()
        ]);
    }
Example #15
Source File: getFields.test.ts    From amplify-codegen with Apache License 2.0 4 votes vote down vote up
describe('getField', () => {
  const nestedType = new GraphQLObjectType({
    name: 'NestedObject',
    fields: () => ({
      level: { type: GraphQLInt },
      subObj: { type: nestedType },
    }),
  });

  const schema = new GraphQLSchema({
    query: new GraphQLObjectType({
      name: 'Query',
      fields: {
        foo: { type: GraphQLInt },
        nested: { type: nestedType },
      },
    }),
  });

  it('should support simple scalar', () => {
    const queries = schema.getQueryType().getFields();
    expect(getFields(queries.foo, schema, 3, { useExternalFragmentForS3Object: false })).toEqual({
      name: 'foo',
      fields: [],
      fragments: [],
      hasBody: false,
    });
    expect(getFragment).not.toHaveBeenCalled();
  });

  it('it should recursively resolve fields up to max depth', () => {
    const queries = schema.getQueryType().getFields();
    expect(getFields(queries.nested, schema, 2, { useExternalFragmentForS3Object: false })).toEqual({
      name: 'nested',
      fields: [
        {
          name: 'level',
          fields: [],
          fragments: [],
          hasBody: false,
        },
        {
          name: 'subObj',
          fields: [
            {
              name: 'level',
              fields: [],
              fragments: [],
              hasBody: false,
            },
          ],
          fragments: [],
          hasBody: true,
        },
      ],
      fragments: [],
      hasBody: true,
    });
  });

  it('should not return anything for complex type when the depth is < 1', () => {
    const queries = schema.getQueryType().getFields();
    expect(getFields(queries.nested, schema, 0, { useExternalFragmentForS3Object: false })).toBeUndefined();
  });
  describe('When type is an Interface', () => {
    beforeEach(() => {
      jest.resetAllMocks();
    });
    const shapeInterfaceType = new GraphQLInterfaceType({
      name: 'Entity',
      fields: {
        name: { type: GraphQLString },
      },
    });
    const rectangleType = new GraphQLObjectType({
      name: 'Rectangle',
      fields: {
        name: { type: GraphQLString },
        length: { type: GraphQLInt },
        width: { type: GraphQLInt },
      },
      interfaces: () => [shapeInterfaceType],
    });

    const circleType = new GraphQLObjectType({
      name: 'Circle',
      fields: {
        name: { type: GraphQLString },
        radius: { type: GraphQLInt },
      },
      interfaces: () => [shapeInterfaceType],
    });

    const schema = new GraphQLSchema({
      query: new GraphQLObjectType({
        name: 'Query',
        fields: {
          shapeInterface: { type: shapeInterfaceType },
        },
      }),
      types: [circleType, rectangleType],
    });

    it('interface - should return fragments of all the implementations', () => {
      const maxDepth = 2;
      const getPossibleTypeSpy = jest.spyOn(schema, 'getPossibleTypes');
      getFields(schema.getQueryType().getFields().shapeInterface, schema, maxDepth, { useExternalFragmentForS3Object: false });
      expect(getPossibleTypeSpy).toHaveBeenCalled();
      expect(getFragment).toHaveBeenCalled();

      const commonField = {
        name: 'name',
        fragments: [],
        hasBody: false,
        fields: [],
      };

      expect(getFragment.mock.calls[0][0]).toEqual(circleType);
      expect(getFragment.mock.calls[0][1]).toEqual(schema);
      expect(getFragment.mock.calls[0][2]).toEqual(maxDepth);
      expect(getFragment.mock.calls[0][3]).toEqual([commonField]);

      expect(getFragment.mock.calls[1][0]).toEqual(rectangleType);
      expect(getFragment.mock.calls[1][1]).toEqual(schema);
      expect(getFragment.mock.calls[1][2]).toEqual(maxDepth);
      expect(getFragment.mock.calls[1][3]).toEqual([commonField]);
    });
  });
  describe('When type is an union', () => {
    beforeEach(() => {
      jest.resetAllMocks();
    });
    const rectangleType = new GraphQLObjectType({
      name: 'Rectangle',
      fields: {
        length: { type: GraphQLInt },
        width: { type: GraphQLInt },
      },
    });

    const circleType = new GraphQLObjectType({
      name: 'Circle',
      fields: {
        radius: { type: GraphQLInt },
      },
    });
    const shapeResultUnion = new GraphQLUnionType({
      name: 'ShapeResultUnion',
      types: [circleType, rectangleType],
    });

    const schema = new GraphQLSchema({
      query: new GraphQLObjectType({
        name: 'Query',
        fields: {
          shapeResult: { type: shapeResultUnion },
        },
      }),
    });

    it('union - should return fragments of all the types', () => {
      const maxDepth = 2;
      const getPossibleTypeSpy = jest.spyOn(schema, 'getPossibleTypes');
      getFields(schema.getQueryType().getFields().shapeResult, schema, maxDepth, { useExternalFragmentForS3Object: false });
      expect(getPossibleTypeSpy).toHaveBeenCalled();
      expect(getFragment).toHaveBeenCalled();

      const commonField = []; // unions don't have to have common field

      expect(getFragment.mock.calls[0][0]).toEqual(circleType);
      expect(getFragment.mock.calls[0][1]).toEqual(schema);
      expect(getFragment.mock.calls[0][2]).toEqual(maxDepth);
      expect(getFragment.mock.calls[0][3]).toEqual(commonField);

      expect(getFragment.mock.calls[1][0]).toEqual(rectangleType);
      expect(getFragment.mock.calls[1][1]).toEqual(schema);
      expect(getFragment.mock.calls[1][2]).toEqual(maxDepth);
      expect(getFragment.mock.calls[1][3]).toEqual(commonField);
    });
  });

  describe('aggregateItems should generate two additional levels', () => {
    beforeEach(() => {
      jest.resetAllMocks();
    });
    const aggregateScalarResult = new GraphQLObjectType({
      name: 'SearchableAggregateScalarResult',
      fields: {
        value: { type: GraphQLFloat },
      },
    });

    const aggregateBucketResultItem = new GraphQLObjectType({
      name: 'SearchableAggregateBucketResultItem',
      fields: {
        key: { type: GraphQLString },
        doc_count: { type: GraphQLInt },
      },
    });

    const aggregateBucketResult = new GraphQLObjectType({
      name: 'SearchableAggregateBucketResult',
      fields: {
        buckets: { type: aggregateBucketResultItem },
      },
    });
    
    const aggregateResult = new GraphQLUnionType({
      name: 'SearchableAggregateGenericResult',
      types: [aggregateScalarResult, aggregateBucketResult],
    });

    const aggregateItemsObject = new GraphQLObjectType({
      name: 'SearchableAggregateResult',
      fields: {
        name: { type: GraphQLString },
        result: { type: aggregateResult },
      },
    });

    const schema = new GraphQLSchema({
      query: new GraphQLObjectType({
        name: 'Query',
        fields: {
          aggregateItems: { type: aggregateItemsObject },
        },
      }),
    });

    it('aggregateItems property should traverse two additional levels to generate required fields with default depth 2', () => {
      const maxDepth = 2;
      const getPossibleTypeSpy = jest.spyOn(schema, 'getPossibleTypes');
      getFields(schema.getQueryType().getFields().aggregateItems, schema, maxDepth, { useExternalFragmentForS3Object: false });
      expect(getPossibleTypeSpy).toHaveBeenCalled();
      expect(getFragment).toHaveBeenCalled();

      const commonField = []; // unions don't have to have common field

      expect(getFragment.mock.calls[0][0]).toEqual(aggregateScalarResult);
      expect(getFragment.mock.calls[0][1]).toEqual(schema);
      expect(getFragment.mock.calls[0][2]).toEqual(maxDepth - 1);
      expect(getFragment.mock.calls[0][3]).toEqual(commonField);

      expect(getFragment.mock.calls[1][0]).toEqual(aggregateBucketResult);
      expect(getFragment.mock.calls[1][1]).toEqual(schema);
      expect(getFragment.mock.calls[1][2]).toEqual(maxDepth - 1);
      expect(getFragment.mock.calls[1][3]).toEqual(commonField);
    });
  });
});
Example #16
Source File: FetcherWriter.ts    From graphql-ts-client with MIT License 4 votes vote down vote up
constructor(
        protected modelType: GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType,
        protected ctx: FetcherContext,
        stream: WriteStream,
        config: GeneratorConfig
    ) {
        super(stream, config);

        this.fetcherTypeName = `${this.modelType.name}${config.fetcherSuffix ?? "Fetcher"}`;

        if (modelType instanceof GraphQLUnionType) {
            const map: { [key: string]: GraphQLField<any, any> } = {};
            const itemCount = modelType.getTypes().length;
            if (itemCount !== 0) {
                const fieldCountMap = new Map<string, number>();
                for (const type of modelType.getTypes()) {
                    for (const fieldName in type.getFields()) {
                        fieldCountMap.set(fieldName, (fieldCountMap.get(fieldName) ?? 0) + 1);
                    }
                }
                const firstTypeFieldMap = modelType.getTypes()[0].getFields();
                for (const fieldName in firstTypeFieldMap) {
                    if (fieldCountMap.get(fieldName) === itemCount) {
                        map[fieldName] = firstTypeFieldMap[fieldName]!;
                    }
                }
            }
            this.fieldMap = map;
        } else {
            this.fieldMap = modelType.getFields();
        }
      
        const fieldArgsMap = new Map<string, GraphQLArgument[]>();
        const fieldCategoryMap = new Map<string, string>();
        const defaultFetcherProps: string[] = [];
        this.hasArgs = false;
        for (const fieldName in this.fieldMap) {
            const field = this.fieldMap[fieldName]!;
            const targetType = targetTypeOf(field.type);
            if (this.modelType.name !== "Query" && 
            this.modelType.name !== "Mutation" && 
            targetType === undefined && 
            field.args.length === 0 &&
            !field.isDeprecated) {
                if (config.defaultFetcherExcludeMap !== undefined) {
                    const excludeProps = config.defaultFetcherExcludeMap[modelType.name];
                    if (excludeProps !== undefined && excludeProps.filter(name => name === fieldName).length !== 0) {
                        continue;
                    }
                }
                defaultFetcherProps.push(fieldName);
            }

            if (field.args.length !== 0) {
                fieldArgsMap.set(fieldName, field.args);
            }

            const fieldCoreType = 
                field.type instanceof GraphQLNonNull ?
                field.type.ofType :
                field.type;
            if (this.ctx.embeddedTypes.has(fieldCoreType)) {
                fieldCategoryMap.set(fieldName, "SCALAR");
            } else if (this.ctx.connections.has(fieldCoreType)) {
                fieldCategoryMap.set(fieldName, "CONNECTION");
            } else if (fieldCoreType instanceof GraphQLList) {
                const elementType = 
                    fieldCoreType.ofType instanceof GraphQLNonNull ?
                    fieldCoreType.ofType.ofType :
                    fieldCoreType.ofType;
                if (elementType instanceof GraphQLObjectType ||
                    elementType instanceof GraphQLInterfaceType ||
                    elementType instanceof GraphQLUnionType
                ) {
                    fieldCategoryMap.set(fieldName, "LIST");
                }
            } else if (fieldCoreType instanceof GraphQLObjectType ||
                fieldCoreType instanceof GraphQLInterfaceType ||
                fieldCoreType instanceof GraphQLUnionType
            ) {
                fieldCategoryMap.set(fieldName, "REFERENCE");
            } else if (this.ctx.idFieldMap.get(this.modelType) === field) {
                fieldCategoryMap.set(fieldName, "ID");
            } else {
                fieldCategoryMap.set(fieldName, "SCALAR");
            }

            if (field.args.length !== 0) {
                this.hasArgs = true;
            }
        }

        this.defaultFetcherProps = defaultFetcherProps;
        this.fieldArgsMap = fieldArgsMap;
        this.fieldCategoryMap = fieldCategoryMap;
        let prefix = instancePrefix(this.modelType.name);
        this.emptyFetcherName = `${prefix}$`;
        this.defaultFetcherName = defaultFetcherProps.length !== 0 ? `${prefix}$$` : undefined;
    }
Example #17
Source File: handler.spec.ts    From graphql-mesh with MIT License 4 votes vote down vote up
describe('odata', () => {
  let pubsub: MeshPubSub;
  let cache: KeyValueCache;
  let store: MeshStore;
  let logger: Logger;
  beforeEach(() => {
    pubsub = new PubSub();
    cache = new InMemoryLRUCache();
    store = new MeshStore('odata', new InMemoryStoreStorageAdapter(), {
      readonly: false,
      validate: false,
    });
    logger = new DefaultLogger('ODataTest');
    resetMocks();
  });
  it('should create a GraphQL schema from a simple OData endpoint', async () => {
    addMock('https://services.odata.org/TripPinRESTierService/$metadata', async () => new Response(TripPinMetadata));
    const handler = new ODataHandler({
      name: 'TripPin',
      config: {
        baseUrl: 'https://services.odata.org/TripPinRESTierService',
        customFetch: mockFetch,
      },
      pubsub,
      cache,
      store,
      baseDir,
      importFn,
      logger,
    });
    const source = await handler.getMeshSource();
    expect(printSchema(source.schema)).toMatchSnapshot();
  });
  it('should create correct GraphQL schema for functions with entity set paths', async () => {
    addMock('http://sample.service.com/$metadata', async () => new Response(BasicMetadata));
    const handler = new ODataHandler({
      name: 'SampleService',
      config: {
        baseUrl: 'http://sample.service.com',
        customFetch: mockFetch,
      },
      pubsub,
      cache,
      store,
      baseDir,
      importFn,
      logger,
    });
    const source = await handler.getMeshSource();
    expect(printSchema(source.schema)).toMatchSnapshot();
  });
  it('should declare arguments for fields created from bound functions', async () => {
    addMock('https://services.odata.org/TripPinRESTierService/$metadata', async () => new Response(TripPinMetadata));
    const handler = new ODataHandler({
      name: 'TripPin',
      config: {
        baseUrl: 'https://services.odata.org/TripPinRESTierService',
        customFetch: mockFetch,
      },
      pubsub,
      cache,
      store,
      baseDir,
      importFn,
      logger,
    });
    const source = await handler.getMeshSource();
    const personType = source.schema.getType('IPerson') as GraphQLInterfaceType;
    const getFriendsTripsFunction = personType.getFields().GetFriendsTrips;
    expect(getFriendsTripsFunction.args).toHaveLength(2);
    const personArg = getFriendsTripsFunction.args.find(arg => arg.name === 'person');
    expect(personArg).not.toBeFalsy();
    expect(personArg.type.toString()).toBe('PersonInput');
    const userNameArg = getFriendsTripsFunction.args.find(arg => arg.name === 'userName');
    expect(userNameArg).not.toBeFalsy();
    expect(userNameArg.type.toString()).toBe('String!');
  });
  it('should generate correct HTTP request for requesting an EntitySet', async () => {
    addMock('https://services.odata.org/TripPinRESTierService/$metadata', async () => new Response(TripPinMetadata));
    const correctUrl = 'https://services.odata.org/TripPinRESTierService/People';
    const correctMethod = 'GET';
    let sentRequest: Request;
    addMock(correctUrl, async request => {
      sentRequest = request;
      return new Response(JSON.stringify({ value: [PersonMockData] }));
    });
    const handler = new ODataHandler({
      name: 'TripPin',
      config: {
        baseUrl: 'https://services.odata.org/TripPinRESTierService',
        customFetch: mockFetch,
      },
      pubsub,
      cache,
      store,
      baseDir,
      importFn,
      logger,
    });
    const source = await handler.getMeshSource();

    const graphqlResult = (await source.executor({
      context: {},
      document: parse(/* GraphQL */ `
        {
          People {
            UserName
            FirstName
          }
        }
      `),
    })) as ExecutionResult;

    expect(graphqlResult.errors).toBeFalsy();
    expect(sentRequest!.method).toBe(correctMethod);
    expect(sentRequest!.url).toBe(correctUrl);
  });
  it('should generate correct HTTP request for requesting a single Entity by ID', async () => {
    addMock('https://services.odata.org/TripPinRESTierService/$metadata', async () => new Response(TripPinMetadata));
    const correctUrl = `https://services.odata.org/TripPinRESTierService/People/SOMEID/`;
    const correctMethod = 'GET';
    let sentRequest: Request;
    addMock(correctUrl, async request => {
      sentRequest = request;
      return new Response(JSON.stringify(PersonMockData));
    });
    const handler = new ODataHandler({
      name: 'TripPin',
      config: {
        baseUrl: 'https://services.odata.org/TripPinRESTierService',
        customFetch: mockFetch,
      },
      pubsub,
      cache,
      store,
      baseDir,
      importFn,
      logger,
    });
    const source = await handler.getMeshSource();

    const graphqlResult = (await source.executor({
      context: {},
      document: parse(/* GraphQL */ `
        {
          PeopleByUserName(UserName: "SOMEID") {
            UserName
            FirstName
          }
        }
      `),
    })) as ExecutionResult;

    expect(graphqlResult.errors).toBeFalsy();
    expect(sentRequest!.method).toBe(correctMethod);
    expect(sentRequest!.url).toBe(correctUrl);
  });
  it('should generate correct HTTP request for requesting a complex property', async () => {
    addMock('https://services.odata.org/TripPinRESTierService/$metadata', async () => new Response(TripPinMetadata));
    const correctUrl = `https://services.odata.org/TripPinRESTierService/Airports/KSFO/?$select=IcaoCode,Location`;
    const correctMethod = 'GET';
    let sentRequest: Request;
    addMock(correctUrl, async request => {
      sentRequest = request;
      return new Response(
        JSON.stringify({
          '@odata.type': 'Microsoft.OData.Service.Sample.TrippinInMemory.Models.Airport',
          IcaoCode: Date.now().toString(),
          Location: {
            '@odata.type': 'Microsoft.OData.Service.Sample.TrippinInMemory.Models.AirportLocation',
            Loc: '',
          },
        })
      );
    });
    const handler = new ODataHandler({
      name: 'TripPin',
      config: {
        baseUrl: 'https://services.odata.org/TripPinRESTierService',
        customFetch: mockFetch,
      },
      pubsub,
      cache,
      store,
      baseDir,
      importFn,
      logger,
    });
    const source = await handler.getMeshSource();

    const graphqlResult = (await source.executor({
      context: {},
      document: parse(/* GraphQL */ `
        {
          AirportsByIcaoCode(IcaoCode: "KSFO") {
            IcaoCode
            Location {
              Loc
            }
          }
        }
      `),
    })) as ExecutionResult;

    expect(graphqlResult.errors).toBeFalsy();
    expect(sentRequest!.method).toBe(correctMethod);
    expect(sentRequest!.url).toBe(correctUrl);
  });
  it('should generate correct HTTP request for query options', async () => {
    addMock('https://services.odata.org/TripPinRESTierService/$metadata', async () => new Response(TripPinMetadata));
    const correctUrl = `https://services.odata.org/TripPinRESTierService/People?$filter=FirstName eq 'Scott'`;
    const correctMethod = 'GET';
    let sentRequest: Request;
    addMock(correctUrl, async request => {
      sentRequest = request;
      return new Response(JSON.stringify({ value: [PersonMockData] }));
    });
    const handler = new ODataHandler({
      name: 'TripPin',
      config: {
        baseUrl: 'https://services.odata.org/TripPinRESTierService',
        customFetch: mockFetch,
      },
      pubsub,
      cache,
      store,
      baseDir,
      importFn,
      logger,
    });
    const source = await handler.getMeshSource();

    const graphqlResult = (await source.executor({
      context: {},
      document: parse(/* GraphQL */ `
        {
          People(queryOptions: { filter: "FirstName eq 'Scott'" }) {
            UserName
            FirstName
          }
        }
      `),
    })) as ExecutionResult;

    expect(graphqlResult.errors).toBeFalsy();
    expect(sentRequest!.method).toBe(correctMethod);
    expect(decodeURIComponent(sentRequest!.url)).toBe(decodeURIComponent(correctUrl));
  });
  it('should generate correct HTTP request for $count', async () => {
    addMock('https://services.odata.org/TripPinRESTierService/$metadata', async () => new Response(TripPinMetadata));
    const correctUrl = `https://services.odata.org/TripPinRESTierService/People/$count`;
    const correctMethod = 'GET';
    let sentRequest: Request;
    addMock(correctUrl, async request => {
      sentRequest = request;
      return new Response(JSON.stringify(20));
    });
    const handler = new ODataHandler({
      name: 'TripPin',
      config: {
        baseUrl: 'https://services.odata.org/TripPinRESTierService',
        customFetch: mockFetch,
      },
      pubsub,
      cache,
      store,
      baseDir,
      importFn,
      logger,
    });
    const source = await handler.getMeshSource();

    const graphqlResult = (await source.executor({
      context: {},
      document: parse(/* GraphQL */ `
        {
          PeopleCount
        }
      `),
    })) as ExecutionResult;

    expect(graphqlResult.errors).toBeFalsy();
    expect(sentRequest!.method).toBe(correctMethod);
    expect(sentRequest!.url).toBe(correctUrl);
  });
  it('should generate correct HTTP request for creating an entity', async () => {
    addMock('https://services.odata.org/TripPinRESTierService/$metadata', async () => new Response(TripPinMetadata));
    const correctUrl = `https://services.odata.org/TripPinRESTierService/People`;
    const correctMethod = 'POST';
    const correctBody = {
      UserName: 'lewisblack',
      FirstName: 'Lewis',
      LastName: 'Black',
      Emails: ['[email protected]'],
      Gender: 'Male',
      FavoriteFeature: 'Feature1',
      Features: ['Feature1', 'Feature2'],
      AddressInfo: [
        {
          Address: '187 Suffolk Ln.',
          City: {
            Name: 'Boise',
            CountryRegion: 'United States',
            Region: 'ID',
          },
        },
      ],
    };
    let sentRequest: any;
    addMock(correctUrl, async request => {
      sentRequest = request.clone();
      const bodyObj = await request.json();
      bodyObj['@odata.type'] = 'Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person';
      return new Response(JSON.stringify(bodyObj));
    });
    const handler = new ODataHandler({
      name: 'TripPin',
      config: {
        baseUrl: 'https://services.odata.org/TripPinRESTierService',
        customFetch: mockFetch,
      },
      pubsub,
      cache,
      store,
      baseDir,
      importFn,
      logger,
    });
    const source = await handler.getMeshSource();

    const graphqlResult = (await source.executor({
      context: {},
      variables: {
        input: correctBody,
      },
      document: parse(/* GraphQL */ `
        mutation CreatePeople($input: PersonInput) {
          createPeople(input: $input) {
            UserName
          }
        }
      `),
    })) as ExecutionResult;

    expect(graphqlResult.errors).toBeFalsy();
    expect(sentRequest!.method).toBe(correctMethod);
    expect(sentRequest!.url).toBe(correctUrl);
    expect(await sentRequest!.json()).toStrictEqual(correctBody);
  });
  it('should generate correct HTTP request for deleting an entity', async () => {
    addMock('https://services.odata.org/TripPinRESTierService/$metadata', async () => new Response(TripPinMetadata));
    const correctUrl = `https://services.odata.org/TripPinRESTierService/People/SOMEID/`;
    const correctMethod = 'DELETE';
    let sentRequest: Request;
    addMock(correctUrl, async request => {
      sentRequest = request;
      return new Response(JSON.stringify({}));
    });
    const handler = new ODataHandler({
      name: 'TripPin',
      config: {
        baseUrl: 'https://services.odata.org/TripPinRESTierService',
        customFetch: mockFetch,
      },
      pubsub,
      cache,
      store,
      baseDir,
      importFn,
      logger,
    });
    const source = await handler.getMeshSource();

    const graphqlResult = (await source.executor({
      context: {},
      document: parse(/* GraphQL */ `
        mutation {
          deletePeopleByUserName(UserName: "SOMEID")
        }
      `),
    })) as ExecutionResult;

    expect(graphqlResult.errors).toBeFalsy();
    expect(sentRequest!.method).toBe(correctMethod);
    expect(sentRequest!.url).toBe(correctUrl);
  });
  it('should generate correct HTTP request for updating an entity', async () => {
    addMock('https://services.odata.org/TripPinRESTierService/$metadata', async () => new Response(TripPinMetadata));
    const correctUrl = `https://services.odata.org/TripPinRESTierService/People/SOMEID/`;
    const correctMethod = 'PATCH';
    const correctBody = {
      FirstName: 'Mirs',
      LastName: 'King',
    };
    let sentRequest: Request;
    addMock(correctUrl, async request => {
      sentRequest = request.clone();
      const returnBody = await request.json();
      returnBody['@odata.type'] = 'Microsoft.OData.Service.Sample.TrippinInMemory.Models.Person';
      return new Response(JSON.stringify(returnBody));
    });
    const handler = new ODataHandler({
      name: 'TripPin',
      config: {
        baseUrl: 'https://services.odata.org/TripPinRESTierService',
        customFetch: mockFetch,
      },
      pubsub,
      cache,
      store,
      baseDir,
      importFn,
      logger,
    });
    const source = await handler.getMeshSource();

    const graphqlResult = (await source.executor({
      context: {},
      variables: {
        UserName: 'SOMEID',
        input: correctBody,
      },
      document: parse(/* GraphQL */ `
        mutation UpdatePeople($UserName: String!, $input: PersonUpdateInput!) {
          updatePeopleByUserName(UserName: $UserName, input: $input) {
            FirstName
          }
        }
      `),
    })) as ExecutionResult;

    expect(graphqlResult.errors).toBeFalsy();
    expect(sentRequest!.method).toBe(correctMethod);
    expect(sentRequest!.url).toBe(correctUrl);
    expect(await sentRequest!.text()).toBe(JSON.stringify(correctBody));
  });
  it('should generate correct HTTP request for invoking unbound functions', async () => {
    addMock('https://services.odata.org/TripPinRESTierService/$metadata', async () => new Response(TripPinMetadata));
    const correctUrl = `https://services.odata.org/TripPinRESTierService/GetNearestAirport(lat = 33, lon = -118)?$select=IcaoCode,Name`;
    const correctMethod = 'GET';
    let sentRequest: Request;
    addMock(correctUrl, async request => {
      sentRequest = request;
      return new Response(
        JSON.stringify({
          '@odata.type': 'Microsoft.OData.Service.Sample.TrippinInMemory.Models.Airport',
          IcaoCode: Date.now().toString(),
          Name: 'Name',
        })
      );
    });
    const handler = new ODataHandler({
      name: 'TripPin',
      config: {
        baseUrl: 'https://services.odata.org/TripPinRESTierService',
        customFetch: mockFetch,
      },
      pubsub,
      cache,
      store,
      baseDir,
      importFn,
      logger,
    });
    const source = await handler.getMeshSource();

    const graphqlResult = (await source.executor({
      context: {},
      document: parse(/* GraphQL */ `
        {
          GetNearestAirport(lat: 33, lon: -118) {
            IcaoCode
            Name
          }
        }
      `),
    })) as ExecutionResult;

    expect(graphqlResult.errors).toBeFalsy();
    expect(sentRequest!.method).toBe(correctMethod);
    expect(decodeURIComponent(sentRequest!.url)).toBe(correctUrl);
  });
  it('should generate correct HTTP request for invoking bound functions', async () => {
    addMock('https://services.odata.org/TripPinRESTierService/$metadata', async () => new Response(TripPinMetadata));
    const correctUrl = `https://services.odata.org/TripPinRESTierService/People/russellwhyte/Trips/0/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetInvolvedPeople?$select=UserName`;
    const correctMethod = 'GET';
    let sentRequest: Request;
    addMock(`https://services.odata.org/TripPinRESTierService/People/russellwhyte/`, async () => {
      return new Response(JSON.stringify(PersonMockData));
    });
    addMock(
      `https://services.odata.org/TripPinRESTierService/People/russellwhyte/Trips?$filter=TripId eq 0&$select=TripId`,
      async () => {
        return new Response(JSON.stringify(TripMockData));
      }
    );
    addMock(correctUrl, async request => {
      sentRequest = request;
      return new Response(
        JSON.stringify({
          value: [],
        })
      );
    });
    const handler = new ODataHandler({
      name: 'TripPin',
      config: {
        baseUrl: 'https://services.odata.org/TripPinRESTierService',
        customFetch: mockFetch,
      },
      pubsub,
      cache,
      store,
      baseDir,
      importFn,
      logger,
    });
    const source = await handler.getMeshSource();

    const graphqlResult = (await source.executor({
      context: {},
      document: parse(/* GraphQL */ `
        {
          PeopleByUserName(UserName: "russellwhyte") {
            UserName
            Trips(queryOptions: { filter: "TripId eq 0" }) {
              TripId
              GetInvolvedPeople {
                UserName
              }
            }
          }
        }
      `),
    })) as ExecutionResult;

    expect(graphqlResult.errors).toBeFalsy();
    expect(sentRequest!.method).toBe(correctMethod);
    expect(sentRequest!.url).toBe(correctUrl);
  });
  it('should generate correct HTTP request for invoking bound functions with arguments', async () => {
    addMock('https://services.odata.org/TripPinRESTierService/$metadata', async () => new Response(TripPinMetadata));
    const correctUrl = `https://services.odata.org/TripPinRESTierService/People/russellwhyte/Microsoft.OData.Service.Sample.TrippinInMemory.Models.GetFriendsTrips(userName='ronaldmundy')?$select=TripId,Name`;
    const correctMethod = 'GET';
    let sentRequest: Request;
    addMock(`https://services.odata.org/TripPinRESTierService/People/russellwhyte/`, async () => {
      return new Response(JSON.stringify(PersonMockData));
    });
    addMock(correctUrl, async request => {
      sentRequest = request;
      return new Response(
        JSON.stringify({
          value: [],
        })
      );
    });
    const handler = new ODataHandler({
      name: 'TripPin',
      config: {
        baseUrl: 'https://services.odata.org/TripPinRESTierService',
        customFetch: mockFetch,
      },
      pubsub,
      cache,
      store,
      baseDir,
      importFn,
      logger,
    });
    const source = await handler.getMeshSource();

    const graphqlResult = (await source.executor({
      context: {},
      document: parse(/* GraphQL */ `
        {
          PeopleByUserName(UserName: "russellwhyte") {
            UserName
            GetFriendsTrips(userName: "ronaldmundy") {
              TripId
              Name
            }
          }
        }
      `),
    })) as ExecutionResult;

    expect(graphqlResult.errors).toBeFalsy();
    expect(sentRequest!.method).toBe(correctMethod);
    expect(sentRequest!.url.replace(/'/g, '%27')).toBe(correctUrl.replace(/'/g, '%27')); // apostrophe gets percent-encoded
  });
  it('should generate correct HTTP request for invoking unbound actions', async () => {
    addMock('https://services.odata.org/TripPinRESTierService/$metadata', async () => new Response(TripPinMetadata));
    const correctUrl = `https://services.odata.org/TripPinRESTierService/ResetDataSource`;
    const correctMethod = 'POST';
    let sentRequest: Request;
    addMock(correctUrl, async request => {
      sentRequest = request;
      return new Response(JSON.stringify(true));
    });
    const handler = new ODataHandler({
      name: 'TripPin',
      config: {
        baseUrl: 'https://services.odata.org/TripPinRESTierService',
        customFetch: mockFetch,
      },
      pubsub,
      cache,
      store,
      baseDir,
      importFn,
      logger,
    });
    const source = await handler.getMeshSource();

    const graphqlResult = (await source.executor({
      context: {},
      document: parse(/* GraphQL */ `
        mutation {
          ResetDataSource
        }
      `),
    })) as ExecutionResult;

    expect(graphqlResult.errors).toBeFalsy();
    expect(sentRequest!.method).toBe(correctMethod);
    expect(sentRequest!.url).toBe(correctUrl);
  });
  it('should generate correct HTTP request for invoking bound actions', async () => {
    addMock('https://services.odata.org/TripPinRESTierService/$metadata', async () => new Response(TripPinMetadata));
    const correctUrl = `https://services.odata.org/TripPinRESTierService/People/russellwhyte/Microsoft.OData.Service.Sample.TrippinInMemory.Models.ShareTrip`;
    const correctMethod = 'POST';
    const correctBody = {
      userName: 'scottketchum',
      tripId: 0,
    };
    let sentRequest: Request;
    addMock(`https://services.odata.org/TripPinRESTierService/People/russellwhyte/`, async () => {
      return new Response(JSON.stringify(PersonMockData));
    });
    addMock(correctUrl, async request => {
      sentRequest = request;
      return new Response(JSON.stringify(true));
    });
    const handler = new ODataHandler({
      name: 'TripPin',
      config: {
        baseUrl: 'https://services.odata.org/TripPinRESTierService',
        customFetch: mockFetch,
      },
      pubsub,
      cache,
      store,
      baseDir,
      importFn,
      logger,
    });
    const source = await handler.getMeshSource();

    const graphqlResult = (await source.executor({
      context: {},
      document: parse(/* GraphQL */ `
        mutation {
          PeopleByUserName(UserName: "russellwhyte") {
            ShareTrip(userName: "scottketchum", tripId: 0)
          }
        }
      `),
    })) as ExecutionResult;

    expect(graphqlResult.errors).toBeFalsy();
    expect(sentRequest!.method).toBe(correctMethod);
    expect(sentRequest!.url).toBe(correctUrl);
    expect(await sentRequest!.text()).toBe(JSON.stringify(correctBody));
  });
});
Example #18
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)
        })
    }
}
Example #19
Source File: selectors.ts    From tql with MIT License 4 votes vote down vote up
transform = (
  ast: DocumentNode,
  schema: GraphQLSchema
): ASTVisitor => {
  // const Field = imp("Field@timkendall@tql");
  // const Argument = imp("Argument@timkendall@tql");
  // const Variable = imp("Variable@timkendall@tql");
  // const InlineFragment = imp("InlineFragment@timkendall@tql");

  return {
    [Kind.DIRECTIVE_DEFINITION]: () => null,

    [Kind.SCALAR_TYPE_DEFINITION]: () => null,

    [Kind.ENUM_TYPE_DEFINITION]: (node) => {
      return null;
    },

    [Kind.ENUM_VALUE_DEFINITION]: (node) => {
      return null;
    },

    [Kind.INPUT_OBJECT_TYPE_DEFINITION]: (def) => {
      return null;
    },

    [Kind.OBJECT_TYPE_DEFINITION]: (node) => {
      const typename = node.name.value;
      const type = schema.getType(typename);

      invariant(
        type instanceof GraphQLObjectType,
        `Type "${typename}" was not instance of expected class GraphQLObjectType.`
      );

      const fields = Object.values(type.getFields());

      return code`
        ${/* selector interface */ ""}
        interface I${type.name}Selector {
          readonly __typename: () => Field<"__typename">
          ${fields.map(printSignature).join("\n")}
        }

        ${/* selector object */ ""}
        const ${typename}Selector: I${typename}Selector = {
          __typename: () => field("__typename"),
          ${fields.map(printMethod).join("\n")}
        }

        ${/* select fn */ ""}
        export const ${toLower(
          typename
        )} = <T extends ReadonlyArray<Selection>>(select: (t: I${typename}Selector) => T) => new SelectionBuilder<ISchema, "${type}", T>(SCHEMA as any, "${typename}", select(${typename}Selector))
      `;
    },

    [Kind.INTERFACE_TYPE_DEFINITION]: (node) => {
      const typename = node.name.value;
      const type = schema.getType(typename);

      invariant(
        type instanceof GraphQLInterfaceType,
        `Type "${typename}" was not instance of expected class GraphQLInterfaceType.`
      );

      // @note Get all implementors of this union
      const implementations = schema
        .getPossibleTypes(type)
        .map((type) => type.name);

      const fields = Object.values(type.getFields());

      return code`
        ${/* selector interface */ ""}
        interface I${type.name}Selector {
          readonly __typename: () => Field<"__typename">
          
          ${fields.map(printSignature).join("\n")}

          readonly on: <T extends ReadonlyArray<Selection>, F extends ${implementations
            .map((name) => `"${name}"`)
            .join(" | ")}>(
            type: F,
            select: (t: ${printConditionalSelectorArg(
              implementations.map((name) => name)
            )}) => T
          ) => InlineFragment<NamedType<F>, SelectionSet<T>>
        }

        ${/* selector object */ ""}
        const ${typename}Selector: I${typename}Selector = {
          __typename: () => field("__typename"),
          
          ${fields.map(printMethod).join("\n")}

          on: (
            type,
            select,
          ) => {
            switch(type) {
              ${implementations
                .map(
                  (name) => `
                case "${name}": {
                  return inlineFragment(
                    namedType("${name}"),
                    selectionSet(select(${name}Selector as Parameters<typeof select>[0])),
                  )
                }
              `
                )
                .join("\n")}
              default:
                throw new TypeConditionError({ 
                  selectedType: type, 
                  abstractType: "${type.name}",
                })
            }
          },
        }

        ${/* select fn */ ""}
        export const ${toLower(
          typename
        )} = <T extends ReadonlyArray<Selection>>(select: (t: I${typename}Selector) => T) => new SelectionBuilder<ISchema, "${type}", T>(SCHEMA as any, "${typename}", select(${typename}Selector))
      `;
    },

    [Kind.UNION_TYPE_DEFINITION]: (node) => {
      const typename = node.name.value;
      const type = schema.getType(typename);

      invariant(
        type instanceof GraphQLUnionType,
        `Type "${typename}" was not instance of expected class GraphQLUnionType.`
      );

      // @note Get all implementors of this union
      const implementations = schema
        .getPossibleTypes(type)
        .map((type) => type.name);

      return code`
        ${/* selector interface */ ""}
        interface I${type.name}Selector {
          readonly __typename: () => Field<"__typename">

          readonly on: <T extends ReadonlyArray<Selection>, F extends ${implementations
            .map((name) => `"${name}"`)
            .join(" | ")}>(
            type: F,
            select: (t: ${printConditionalSelectorArg(
              implementations.map((name) => name)
            )}) => T
          ) => InlineFragment<NamedType<F>, SelectionSet<T>>
        }

        ${/* selector object */ ""}
        const ${typename}Selector: I${typename}Selector = {
          __typename: () => field("__typename"),
          
          on: (
            type,
            select,
          ) => {
            switch(type) {
              ${implementations
                .map(
                  (name) => `
                case "${name}": {
                  return inlineFragment(
                    namedType("${name}"),
                    selectionSet(select(${name}Selector as Parameters<typeof select>[0])),
                  )
                }
              `
                )
                .join("\n")}
              default:
                throw new TypeConditionError({ 
                  selectedType: type, 
                  abstractType: "${type.name}",
                })
            }
          },
        }

        ${/* select fn */ ""}
        export const ${toLower(
          typename
        )} = <T extends ReadonlyArray<Selection>>(select: (t: I${typename}Selector) => T) => new SelectionBuilder<ISchema, "${type}", T>(SCHEMA as any, "${typename}", select(${typename}Selector))
      `;
    },

    [Kind.SCHEMA_DEFINITION]: (node) => {
      return null;
    },
  };
}
Example #20
Source File: require-id-when-available.ts    From graphql-eslint with MIT License 4 votes vote down vote up
rule: GraphQLESLintRule<[RequireIdWhenAvailableRuleConfig], true> = {
  meta: {
    type: 'suggestion',
    // eslint-disable-next-line eslint-plugin/require-meta-has-suggestions
    hasSuggestions: true,
    docs: {
      category: 'Operations',
      description: 'Enforce selecting specific fields when they are available on the GraphQL type.',
      url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
      requiresSchema: true,
      requiresSiblings: true,
      examples: [
        {
          title: 'Incorrect',
          code: /* GraphQL */ `
            # In your schema
            type User {
              id: ID!
              name: String!
            }

            # Query
            query {
              user {
                name
              }
            }
          `,
        },
        {
          title: 'Correct',
          code: /* GraphQL */ `
            # In your schema
            type User {
              id: ID!
              name: String!
            }

            # Query
            query {
              user {
                id
                name
              }
            }

            # Selecting \`id\` with an alias is also valid
            query {
              user {
                id: name
              }
            }
          `,
        },
      ],
      recommended: true,
    },
    messages: {
      [RULE_ID]:
        "Field{{ pluralSuffix }} {{ fieldName }} must be selected when it's available on a type.\nInclude it in your selection set{{ addition }}.",
    },
    schema: {
      definitions: {
        asString: {
          type: 'string',
        },
        asArray: ARRAY_DEFAULT_OPTIONS,
      },
      type: 'array',
      maxItems: 1,
      items: {
        type: 'object',
        additionalProperties: false,
        properties: {
          fieldName: {
            oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asArray' }],
            default: DEFAULT_ID_FIELD_NAME,
          },
        },
      },
    },
  },
  create(context) {
    const schema = requireGraphQLSchemaFromContext(RULE_ID, context);
    const siblings = requireSiblingsOperations(RULE_ID, context);
    const { fieldName = DEFAULT_ID_FIELD_NAME } = context.options[0] || {};
    const idNames = asArray(fieldName);

    // Check selections only in OperationDefinition,
    // skip selections of OperationDefinition and InlineFragment
    const selector = 'OperationDefinition SelectionSet[parent.kind!=/(^OperationDefinition|InlineFragment)$/]';
    const typeInfo = new TypeInfo(schema);

    function checkFragments(node: GraphQLESTreeNode<SelectionSetNode>): void {
      for (const selection of node.selections) {
        if (selection.kind !== Kind.FRAGMENT_SPREAD) {
          continue;
        }

        const [foundSpread] = siblings.getFragment(selection.name.value);
        if (!foundSpread) {
          continue;
        }

        const checkedFragmentSpreads = new Set<string>();
        const visitor = visitWithTypeInfo(typeInfo, {
          SelectionSet(node, key, parent: ASTNode) {
            if (parent.kind === Kind.FRAGMENT_DEFINITION) {
              checkedFragmentSpreads.add(parent.name.value);
            } else if (parent.kind !== Kind.INLINE_FRAGMENT) {
              checkSelections(node, typeInfo.getType(), selection.loc.start, parent, checkedFragmentSpreads);
            }
          },
        });

        visit(foundSpread.document, visitor);
      }
    }

    function checkSelections(
      node: OmitRecursively<SelectionSetNode, 'loc'>,
      type: GraphQLOutputType,
      // Fragment can be placed in separate file
      // Provide actual fragment spread location instead of location in fragment
      loc: ESTree.Position,
      // Can't access to node.parent in GraphQL AST.Node, so pass as argument
      parent: any,
      checkedFragmentSpreads = new Set<string>()
    ): void {
      const rawType = getBaseType(type);
      const isObjectType = rawType instanceof GraphQLObjectType;
      const isInterfaceType = rawType instanceof GraphQLInterfaceType;

      if (!isObjectType && !isInterfaceType) {
        return;
      }
      const fields = rawType.getFields();
      const hasIdFieldInType = idNames.some(name => fields[name]);

      if (!hasIdFieldInType) {
        return;
      }

      function hasIdField({ selections }: typeof node): boolean {
        return selections.some(selection => {
          if (selection.kind === Kind.FIELD) {
            if (selection.alias && idNames.includes(selection.alias.value)) {
              return true;
            }

            return idNames.includes(selection.name.value);
          }

          if (selection.kind === Kind.INLINE_FRAGMENT) {
            return hasIdField(selection.selectionSet);
          }

          if (selection.kind === Kind.FRAGMENT_SPREAD) {
            const [foundSpread] = siblings.getFragment(selection.name.value);
            if (foundSpread) {
              const fragmentSpread = foundSpread.document;
              checkedFragmentSpreads.add(fragmentSpread.name.value);
              return hasIdField(fragmentSpread.selectionSet);
            }
          }
          return false;
        });
      }

      const hasId = hasIdField(node);

      checkFragments(node as GraphQLESTreeNode<SelectionSetNode>);

      if (hasId) {
        return;
      }

      const pluralSuffix = idNames.length > 1 ? 's' : '';
      const fieldName = englishJoinWords(idNames.map(name => `\`${(parent.alias || parent.name).value}.${name}\``));

      const addition =
        checkedFragmentSpreads.size === 0
          ? ''
          : ` or add to used fragment${checkedFragmentSpreads.size > 1 ? 's' : ''} ${englishJoinWords(
              [...checkedFragmentSpreads].map(name => `\`${name}\``)
            )}`;

      const problem: ReportDescriptor = {
        loc,
        messageId: RULE_ID,
        data: {
          pluralSuffix,
          fieldName,
          addition,
        },
      };

      // Don't provide suggestions for selections in fragments as fragment can be in a separate file
      if ('type' in node) {
        problem.suggest = idNames.map(idName => ({
          desc: `Add \`${idName}\` selection`,
          fix: fixer => fixer.insertTextBefore((node as any).selections[0], `${idName} `),
        }));
      }
      context.report(problem);
    }

    return {
      [selector](node: GraphQLESTreeNode<SelectionSetNode, true>) {
        const typeInfo = node.typeInfo();
        if (typeInfo.gqlType) {
          checkSelections(node, typeInfo.gqlType, node.loc.start, node.parent);
        }
      },
    };
  },
}
Example #21
Source File: types.ts    From tql with MIT License 4 votes vote down vote up
transform = (
  ast: DocumentNode,
  schema: GraphQLSchema
): ASTVisitor => {
  // @note needed to serialize inline enum values correctly at runtime
  const enumValues = new Set<string>();

  return {
    [Kind.DIRECTIVE_DEFINITION]: () => null,

    [Kind.SCALAR_TYPE_DEFINITION]: () => null,

    [Kind.ENUM_TYPE_DEFINITION]: (node) => {
      const typename = node.name.value;
      const values = node.values?.map((v) => v.name.value) ?? [];

      const printMember = (member: string): string => {
        return `${member} = "${member}"`;
      };

      return code`
        export enum ${typename} {
          ${values.map(printMember).join(",\n")}
        }
      `;
    },

    [Kind.ENUM_VALUE_DEFINITION]: (node) => {
      enumValues.add(node.name.value);
      return null;
    },

    [Kind.INPUT_OBJECT_TYPE_DEFINITION]: (node) => {
      const typename = node.name.value;
      const type = schema.getType(typename);

      invariant(
        type instanceof GraphQLInputObjectType,
        `Type "${typename}" was not instance of expected class GraphQLInputObjectType.`
      );

      const fields = Object.values(type.getFields());

      const printField = (field: GraphQLInputField) => {
        const isList = listType(field.type);
        const isNonNull = isNonNullType(field.type);
        const baseType = inputType(field.type);

        let tsType: string;

        if (baseType instanceof GraphQLScalarType) {
          tsType = toPrimitive(baseType);
        } else if (baseType instanceof GraphQLEnumType) {
          tsType = baseType.name;
        } else if (baseType instanceof GraphQLInputObjectType) {
          tsType = "I" + baseType.name;
        } else {
          throw new Error("Unable to render inputField!");
        }

        return [
          field.name,
          isNonNull ? ":" : "?:",
          " ",
          tsType,
          isList ? "[]" : "",
        ].join("");
      };

      return code`
        export interface I${typename} {
          ${fields.map(printField).join("\n")}
        }
      `;
    },

    [Kind.OBJECT_TYPE_DEFINITION]: (node) => {
      const typename = node.name.value;
      const type = schema.getType(typename);

      invariant(
        type instanceof GraphQLObjectType,
        `Type "${typename}" was not instance of expected class GraphQLObjectType.`
      );

      const fields = Object.values(type.getFields());
      const interfaces = type.getInterfaces();

      // @note TypeScript only requires new fields to be defined on interface extendors
      const interfaceFields = interfaces.flatMap((i) =>
        Object.values(i.getFields()).map((field) => field.name)
      );
      const uncommonFields = fields.filter(
        (field) => !interfaceFields.includes(field.name)
      );

      // @todo extend any implemented interfaces
      // @todo only render fields unique to this type
      const extensions =
        interfaces.length > 0
          ? `extends ${interfaces.map((i) => "I" + i.name).join(", ")}`
          : "";

      return code`
        export interface I${typename} ${extensions} {
          readonly __typename: ${`"${typename}"`}
          ${uncommonFields.map(printField).join("\n")}
        }
      `;
    },

    [Kind.INTERFACE_TYPE_DEFINITION]: (node) => {
      const typename = node.name.value;
      const type = schema.getType(typename);

      invariant(
        type instanceof GraphQLInterfaceType,
        `Type "${typename}" was not instance of expected class GraphQLInterfaceType.`
      );

      // @note Get all implementors of this union
      const implementations = schema
        .getPossibleTypes(type)
        .map((type) => type.name);

      const fields = Object.values(type.getFields());

      return code`
        export interface I${typename} {
          readonly __typename: ${implementations
            .map((type) => `"${type}"`)
            .join(" | ")}
          ${fields.map(printField).join("\n")}
        }
      `;
    },

    [Kind.UNION_TYPE_DEFINITION]: (node) => {
      const typename = node.name.value;
      const type = schema.getType(typename);

      invariant(
        type instanceof GraphQLUnionType,
        `Type "${typename}" was not instance of expected class GraphQLUnionType.`
      );

      // @note Get all implementors of this union
      const implementations = schema
        .getPossibleTypes(type)
        .map((type) => type.name);

      return code`
        export type ${"I" + type.name} = ${implementations
        .map((type) => `I${type}`)
        .join(" | ")}
      `;
    },

    [Kind.SCHEMA_DEFINITION]: (_) => {
      return null;
    },
  };
}