graphql#GraphQLResolveInfo TypeScript Examples

The following examples show how to use graphql#GraphQLResolveInfo. 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: user.resolver.ts    From prisma-nestjs-graphql with MIT License 6 votes vote down vote up
/**
   * Query for single user.
   */
  @Query(() => [User])
  async users(@Args('where') where: UserWhereInput, @Info() info: GraphQLResolveInfo) {
    const select = new PrismaSelect(info).value;
    // console.log('select', select);
    return await prisma.user.findMany({ where, ...select });
  }
Example #2
Source File: book.resolver.ts    From mikro-orm-graphql-example with MIT License 6 votes vote down vote up
@Mutation(() => Book)
  public async updateBook(
    @Arg('input') input: BookValidator,
    @Arg('id') id: string,
    @Ctx() ctx: MyContext,
    @Info() info: GraphQLResolveInfo,
  ): Promise<Book> {
    const relationPaths = fieldsToRelations(info);
    const book = await ctx.em.getRepository(Book).findOneOrFail({ id }, relationPaths);
    book.assign(input);
    await ctx.em.persist(book).flush();
    return book;
  }
Example #3
Source File: book.resolver.ts    From mikro-orm-graphql-example with MIT License 6 votes vote down vote up
@Mutation(() => Book)
  public async addBook(
    @Arg('input') input: BookValidator,
    @Arg('authorId') authorId: string,
    @Arg('publisherId', { nullable: true }) publisherId: string,
    @Ctx() ctx: MyContext,
    @Info() info: GraphQLResolveInfo,
  ): Promise<Book> {
    const book = new Book(input);
    book.author = await ctx.em
      .getRepository(Author)
      .findOneOrFail({ id: authorId }, fieldsToRelations(info, { root: 'author' }));

    if (publisherId) {
      book.publisher = await ctx.em.getRepository(Publisher).findOneOrFail(
        { id: publisherId },
        fieldsToRelations(info, {
          root: 'publisher',
        }),
      );
    }
    await ctx.em.persist(book).flush();
    return book;
  }
Example #4
Source File: book.resolver.ts    From mikro-orm-graphql-example with MIT License 6 votes vote down vote up
@Query(() => Book, { nullable: true })
  public async getBook(
    @Arg('id') id: string,
    @Ctx() ctx: MyContext,
    @Info() info: GraphQLResolveInfo,
  ): Promise<Book | null> {
    const relationPaths = fieldsToRelations(info);
    return ctx.em.getRepository(Book).findOne({ id }, relationPaths);
  }
Example #5
Source File: author.resolver.ts    From mikro-orm-graphql-example with MIT License 6 votes vote down vote up
@Mutation(() => Author)
  public async updateAuthor(
    @Arg('input') input: AuthorValidator,
    @Arg('id') id: string,
    @Ctx() ctx: MyContext,
    @Info() info: GraphQLResolveInfo,
  ): Promise<Author> {
    const relationPaths = fieldsToRelations(info);
    const author = await ctx.em.getRepository(Author).findOneOrFail({ id }, relationPaths);
    author.assign(input);
    await ctx.em.persist(author).flush();
    return author;
  }
Example #6
Source File: author.resolver.ts    From mikro-orm-graphql-example with MIT License 6 votes vote down vote up
@Query(() => Author, { nullable: true })
  public async getAuthor(
    @Arg('id') id: string,
    @Ctx() ctx: MyContext,
    @Info() info: GraphQLResolveInfo,
  ): Promise<Author | null> {
    const relationPaths = fieldsToRelations(info);
    return ctx.em.getRepository(Author).findOne({ id }, relationPaths);
  }
Example #7
Source File: complexity.ts    From backend with MIT License 6 votes vote down vote up
function enforceAccumulatedLimit(info: GraphQLResolveInfo, context: LimitedQueryContext, cardinality: number) {
    if (!context.limits) {
        context.limits = {};
    }

    /* In a nested query such as pupils { subcourses { ... } } this will be called twice,
       once for the pupil (annotated with LimitedQuery) and then for each pupil for the subcourses (annotated with LimitEstimated).
       Thus this function will be executed once for the pupil, and the limit (specified by TAKE) will be stored in the context.limits
       In the subcourse, the path is { key: "subcourses", prev: { key: 0, prev: { key: "pupils"}}}, thus with .prev.prev one can access the limit of the previous association.
       If the query retrieves 100 pupils, and 10 subcourses for each pupil, by multiplying we get the maximum number of pupils 100 * 10. On that we enforce the limit
       NOTE: Keys of different paths could collide in a query, however we hope that nobody writes such query :)
    */
    let accumulatedLimit = (info.path?.prev?.prev && context.limits[ info.path.prev.prev.key ]) ?? 1;
    accumulatedLimit *= cardinality;

    context.limits[ info.path.key ] = accumulatedLimit;

    if (accumulatedLimit > ACCUMULATED_LIMIT) {
        const limitInfo = Object.entries(context.limits)
            .map(([key, limit]) => `${key}:${limit}`)
            .join(", ");

        throw new ValidationError(`Overcomplex Query: The nested query might return more than 1000 entries (${limitInfo})`);
    }
}
Example #8
Source File: requestedFields.ts    From squid with GNU General Public License v3.0 6 votes vote down vote up
export function connectionRequestedFields(model: Model, entityName: string, info: GraphQLResolveInfo): ConnectionRequestedFields {
    let requested: ConnectionRequestedFields = {}
    let tree = getResolveTree(info, toPlural(entityName) + 'Connection')
    requested.totalCount = hasTreeRequest(tree.fields, 'totalCount')
    requested.pageInfo = hasTreeRequest(tree.fields, 'pageInfo')
    let edgesTree = getTreeRequest(tree.fields, 'edges')
    if (edgesTree) {
        let edgeFields = simplifyResolveTree(info.schema, edgesTree, entityName + 'Edge').fields
        requested.edges = {}
        requested.edges.cursor = hasTreeRequest(edgeFields, 'cursor')
        let nodeTree = getTreeRequest(edgeFields, 'node')
        if (nodeTree) {
            requested.edges.node = collectRequestedFields(model, entityName, info.schema, nodeTree)
        }
    }
    return requested
}
Example #9
Source File: requestedFields.ts    From squid with GNU General Public License v3.0 6 votes vote down vote up
export function ftsRequestedFields(model: Model, queryName: string, info: GraphQLResolveInfo): FtsRequestedFields {
    let query = model[queryName]
    assert(query.kind == 'fts')

    let requested: FtsRequestedFields = {}
    let tree = getResolveTree(info, queryName + '_Output')

    requested.rank = hasTreeRequest(tree.fields, 'rank')
    requested.highlight = hasTreeRequest(tree.fields, 'highlight')

    let itemTree = getTreeRequest(tree.fields, 'item')
    if (itemTree) {
        requested.item = {}
        for (let i = 0; i < query.sources.length; i++) {
            let entity = query.sources[i].entity
            let fields = collectRequestedFields(model, entity, info.schema, itemTree)
            for (let key in fields) {
                requested.item[entity] = fields
                break
            }
        }
    }

    return requested
}
Example #10
Source File: compute-snapshot-file-path.ts    From graphql-mesh with MIT License 6 votes vote down vote up
export function computeSnapshotFilePath(options: {
  info: GraphQLResolveInfo;
  args: any;
  outputDir: string;
  respectSelectionSet?: boolean;
}) {
  const typeName = options.info.parentType.name;
  const fieldName = options.info.fieldName;
  const hashObj = options.respectSelectionSet
    ? {
        args: options.args,
        fieldNodes: options.info.fieldNodes,
      }
    : options.args;
  const hash = objectHash(hashObj, { ignoreUnknown: true }).toString();
  const fileName = [typeName, fieldName, hash].join('_') + '.json';
  return path.join(options.outputDir, fileName);
}
Example #11
Source File: requestedFields.ts    From squid with GNU General Public License v3.0 6 votes vote down vote up
function getResolveTree(info: GraphQLResolveInfo, typeName?: string): ResolveTree {
    let tree = parseResolveInfo(info)
    assert(isResolveTree(tree))
    if (typeName) {
        return simplifyResolveTree(info.schema, tree, typeName)
    } else {
        return tree
    }
}
Example #12
Source File: compute-cache-key.ts    From graphql-mesh with MIT License 6 votes vote down vote up
export function computeCacheKey(options: {
  keyStr: string | undefined;
  args: Record<string, any>;
  info: GraphQLResolveInfo;
}): string {
  const argsHash = options.args ? hashObject(options.args) : '';
  const fieldNamesHash = hashObject(options.info.fieldNodes);

  if (!options.keyStr) {
    return `${options.info.parentType.name}-${options.info.fieldName}-${argsHash}-${fieldNamesHash}`;
  }

  const templateData = {
    typeName: options.info.parentType.name,
    fieldName: options.info.fieldName,
    args: options.args,
    argsHash,
    fieldNamesHash,
    info: options.info || null,
    env: process.env,
  };

  return stringInterpolator.parse(options.keyStr, templateData);
}
Example #13
Source File: addExecutionLogicToComposer.ts    From graphql-mesh with MIT License 6 votes vote down vote up
function linkResolver(
  linkObjArgs: any,
  actualResolver: GraphQLFieldResolver<any, any>,
  root: any,
  args: any,
  context: any,
  info: GraphQLResolveInfo
) {
  for (const argKey in linkObjArgs) {
    const argInterpolation = linkObjArgs[argKey];
    const actualValue = stringInterpolator.parse(argInterpolation, {
      root,
      args,
      context,
      info,
      env: process.env,
    });
    lodashSet(args, argKey, actualValue);
    lodashSet(args, `input.${argKey}`, actualValue);
  }
  return actualResolver(root, args, context, info);
}
Example #14
Source File: schema-resolver.ts    From graphql-mesh with MIT License 6 votes vote down vote up
private createInterfaceTypeConfig(soapType: SoapObjectType): GraphQLInterfaceTypeConfig<any, any> {
    const fields = (): GraphQLFieldConfigMap<any, any> => {
      const fieldMap: GraphQLFieldConfigMap<any, any> = {};
      this.appendInterfaceTypeFields(fieldMap, soapType);
      return fieldMap;
    };

    return {
      name: this.options.interfaceNameResolver(soapType),
      fields,
      // should never be called, since the schema will not contain ambigous return types
      resolveType: (value: any, context: any, info: GraphQLResolveInfo) => {
        throw Error('no interface resolving available');
      },
    };
  }
Example #15
Source File: schema-resolver.ts    From graphql-mesh with MIT License 6 votes vote down vote up
createSoapOperationFieldResolver<TSource, TContext>(
    operation: SoapOperation
  ): GraphQLFieldResolver<TSource, { [argName: string]: any }, TContext> {
    return async (
      graphqlSource: TSource,
      graphqlArgs: { [argName: string]: any },
      graphqlContext: TContext,
      graphqlInfo: GraphQLResolveInfo
    ) => {
      return this.soapCaller.call({
        operation,
        graphqlSource,
        graphqlArgs,
        graphqlContext,
        graphqlInfo,
      });
    };
  }
Example #16
Source File: resolver.ts    From squid with GNU General Public License v3.0 5 votes vote down vote up
function aliasResolver(source: any, args: unknown, ctx: unknown, info: GraphQLResolveInfo): any {
    return source[info.path.key]
}
Example #17
Source File: getTypeResolverFromOutputTCs.ts    From graphql-mesh with MIT License 5 votes vote down vote up
export function getTypeResolverFromOutputTCs(
  ajv: Ajv,
  outputTypeComposers: (ObjectTypeComposer | UnionTypeComposer)[],
  statusCodeOneOfIndexMap?: Record<string, number>
): GraphQLTypeResolver<any, any> {
  const statusCodeTypeMap = new Map<string, ObjectTypeComposer | UnionTypeComposer>();
  for (const statusCode in statusCodeOneOfIndexMap) {
    statusCodeTypeMap.set(statusCode.toString(), outputTypeComposers[statusCodeOneOfIndexMap[statusCode]]);
  }
  return function resolveType(data: any, context: any, info: GraphQLResolveInfo) {
    if (data.__typename) {
      return data.__typename;
    } else if (data.resourceType) {
      return data.resourceType;
    }
    if (data.$response && statusCodeOneOfIndexMap) {
      const responseData: ResponseData = data.$response;
      const type = statusCodeTypeMap.get(responseData.status.toString()) || statusCodeTypeMap.get('default');
      if (type) {
        if ('getFields' in type) {
          return type.getTypeName();
        } else {
          return type.getResolveType()(data, context, info, type.getType());
        }
      }
    }
    const validationErrors: Record<string, ErrorObject[]> = {};
    for (const outputTypeComposer of outputTypeComposers) {
      const validateFn = outputTypeComposer.getExtension('validateWithJSONSchema') as ValidateFunction;
      if (validateFn) {
        const isValid = validateFn(data);
        const typeName = outputTypeComposer.getTypeName();
        if (isValid) {
          if ('getFields' in outputTypeComposer) {
            return outputTypeComposer.getTypeName();
          } else {
            return outputTypeComposer.getResolveType()(data, context, info, outputTypeComposer.getType());
          }
        }
        validationErrors[typeName] = ajv.errors || validateFn.errors;
      }
    }
    if (data.$response) {
      const responseData: ResponseData = data.$response;
      const error = new GraphQLError(
        `HTTP Error: ${responseData.status}`,
        undefined,
        undefined,
        undefined,
        undefined,
        undefined,
        {
          ...responseData,
          responseJson: data,
        }
      );
      console.error(error);
      return error;
    }
    const error = new GraphQLError(`Received data doesn't met the union`, null, null, null, null, null, {
      validationErrors,
    });
    console.error(error);
    return error;
  };
}
Example #18
Source File: index.ts    From graphql-mesh with MIT License 5 votes vote down vote up
function getFieldsFromResolveInfo(info: GraphQLResolveInfo) {
  const fieldMap: Record<string, any> = graphqlFields(info);
  return Object.keys(fieldMap).filter(
    fieldName => Object.keys(fieldMap[fieldName]).length === 0 && fieldName !== '__typename'
  );
}
Example #19
Source File: index.ts    From graphql-fields-to-relations with MIT License 5 votes vote down vote up
fieldsToRelations = (
  info: GraphQLResolveInfo,
  options: { depth?: number; root?: string; excludeFields?: string[] } = {
    depth: undefined,
    root: '',
    excludeFields: [],
  },
): string[] => {
  const paths: string[][] = [];

  const nested = (field: any, key: string = undefined as any, deep = 0, parent: string[] = []) => {
    if (Object.values(field).length === 0) {
      return;
    }

    if (deep > 0 || !!options.root) {
      parent.push(key);
      if (
        parent.slice(!options.root ? 0 : options.root?.split('.').length).length > 0 &&
        parent.slice(0, !options.root ? 0 : options.root?.split('.').length).toString() ===
          (!options.root ? '' : options.root?.split('.').toString())
      ) {
        const path = parent.slice(!options.root ? 0 : options.root?.split('.').length);
        paths.push(path);
      }
    }

    Object.keys(field).forEach((key: any) => {
      if (Object.values(field[key]).length > 0 && !!options.depth ? deep < options.depth : true) {
        nested(field[key], key, deep + 1, [...parent]);
      }
    });
  };

  const value = !options.root
    ? graphqFields(info, {}, { excludedFields: options.excludeFields })
    : options.root.split('.').reduce(function (p, prop) {
        return p[prop];
      }, graphqFields(info, {}, { excludedFields: options.excludeFields }));

  nested(value, !!options.root ? options.root.split('.').pop() : undefined);

  return paths.map((list: string[]) => list.join('.'));
}
Example #20
Source File: author.resolver.ts    From mikro-orm-graphql-example with MIT License 5 votes vote down vote up
@Query(() => [Author])
  public async getAuthors(@Ctx() ctx: MyContext, @Info() info: GraphQLResolveInfo): Promise<Author[]> {
    const relationPaths = fieldsToRelations(info);
    return ctx.em.getRepository(Author).findAll(relationPaths);
  }
Example #21
Source File: resolver_builder.ts    From graphql-mesh with MIT License 5 votes vote down vote up
/**
 * From the info object provided by the resolver, get the unique identifier of
 * the parent object
 */
function getParentIdentifier(info: GraphQLResolveInfo): string {
  return getIdentifierRecursive(info.path.prev);
}
Example #22
Source File: book.resolver.ts    From mikro-orm-graphql-example with MIT License 5 votes vote down vote up
@Query(() => [Book])
  public async getBooks(@Ctx() ctx: MyContext, @Info() info: GraphQLResolveInfo): Promise<Book[]> {
    const relationPaths = fieldsToRelations(info);
    return ctx.em.getRepository(Book).findAll(relationPaths);
  }
Example #23
Source File: resolver_builder.ts    From graphql-mesh with MIT License 5 votes vote down vote up
/**
 * From the info object provided by the resolver, get a unique identifier, which
 * is the path formed from the nested field names (or aliases if provided)
 *
 * Used to store and retrieve the _openAPIToGraphQL of parent field
 */
function getIdentifier(info: GraphQLResolveInfo): string {
  return getIdentifierRecursive(info.path);
}
Example #24
Source File: resolver.ts    From squid with GNU General Public License v3.0 5 votes vote down vote up
async function resolveEntityConnection(
    model: Model,
    dialect: Dialect,
    entityName: string,
    args: ConnectionArgs,
    info: GraphQLResolveInfo,
    db: Database
): Promise<ConnectionResponse> {
    let response: ConnectionResponse = {}

    let orderBy = args.orderBy && ensureArray(args.orderBy)
    if (!orderBy?.length) {
        throw new UserInputError('orderBy argument is required for connection')
    }

    let {offset, limit} = decodeConnectionArgs(args)
    let listArgs = {
        where: args.where,
        orderBy,
        offset,
        limit: limit + 1
    }

    // https://relay.dev/assets/files/connections-932f4f2cdffd79724ac76373deb30dc8.htm#sec-undefined.PageInfo.Fields
    function pageInfo(listLength: number): PageInfo {
        return {
            hasNextPage: listLength > limit,
            hasPreviousPage: listLength > 0 && offset > 0,
            startCursor: listLength > 0 ? encodeCursor(offset + 1) : '',
            endCursor: listLength > 0 ? encodeCursor(offset + Math.min(limit, listLength)) : ''
        }
    }

    let fields = connectionRequestedFields(model, entityName, info)
    if (fields.edges?.node) {
        let nodes = await new QueryBuilder(model, dialect, db).executeSelect(entityName, listArgs, fields.edges.node)
        let edges: ConnectionEdge<any>[] = new Array(Math.min(limit, nodes.length))
        for (let i = 0; i < edges.length; i++) {
            edges[i] = {
                node: nodes[i],
                cursor: encodeCursor(offset + i + 1)
            }
        }
        response.edges = edges
        response.pageInfo = pageInfo(nodes.length)
        if (nodes.length > 0 && nodes.length <= limit) {
            response.totalCount = offset + nodes.length
        }
    } else if (fields.edges?.cursor || fields.pageInfo) {
        let listLength = await new QueryBuilder(model, dialect, db).executeListCount(entityName, listArgs)
        response.pageInfo = pageInfo(listLength)
        if (fields.edges?.cursor) {
            response.edges = []
            for (let i = 0; i < Math.min(limit, listLength); i++) {
                response.edges.push({
                    cursor: encodeCursor(offset + i + 1)
                })
            }
        }
        if (listLength > 0 && listLength <= limit) {
            response.totalCount = offset + listLength
        }
    }

    if (fields.totalCount && response.totalCount == null) {
        response.totalCount = await new QueryBuilder(model, dialect, db).executeSelectCount(entityName, listArgs.where)
    }

    return response
}
Example #25
Source File: requestedFields.ts    From squid with GNU General Public License v3.0 5 votes vote down vote up
export function requestedFields(model: Model, entityName: string, info: GraphQLResolveInfo): RequestedFields {
    let tree = getResolveTree(info)
    return collectRequestedFields(model, entityName, info.schema, tree)
}
Example #26
Source File: tracing.interceptor.ts    From nestjs-jaeger-tracing with MIT License 4 votes vote down vote up
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
    const contextType = context.getType<GqlContextType>();
    const except = this.reflector.get<boolean>(
      EXCEPT_TRACING,
      context.getHandler(),
    );
    if (contextType === 'ws') {
      return next.handle();
    }
    if (except) {
      if (contextType === 'rpc') {
        const ctx = context.switchToRpc();
        const data = ctx.getData<TracingContext>();
        if (isTracingContext(data)) {
          context.getArgs()[0] = data.payload;
        }
      }
      return next.handle();
    }
    let parentSpanContext: SpanContext;
    const spanTags = new Map<string, unknown>();
    const spanLogs: Array<Record<string, unknown>> = [];
    const constructorRef = context.getClass().name;
    const handler = context.getHandler().name;
    const operation = [contextType, constructorRef, handler].join(':');
    const tracingData: TracingData = { operation };
    spanTags.set('operation', operation);
    spanTags.set(Tags.COMPONENT, contextType);
    if (contextType === 'graphql') {
      const ctx = GqlExecutionContext.create(context);
      ctx.getContext<TracingObject>().tracing = tracingData;
      const { path } = ctx.getInfo<GraphQLResolveInfo>();
      spanTags.set('graphql.name', `${path?.key}`);
      spanTags.set('graphql.type', `${path?.typename}`.toLowerCase());
    }
    if (contextType === 'http') {
      const ctx = context.switchToHttp();
      ctx.getRequest<TracingObject>().tracing = tracingData;
      const { statusCode } = ctx.getResponse<Response>();
      const { headers, method, path, ip, ips } = ctx.getRequest<Request>();
      spanTags.set(Tags.HTTP_URL, path);
      spanTags.set(Tags.HTTP_METHOD, `${method}`.toUpperCase());
      spanTags.set(Tags.PEER_HOST_IPV4, [ip, ...ips].join(', '));
      spanTags.set(Tags.HTTP_STATUS_CODE, statusCode);
      parentSpanContext = this.tracerProvider.extractHeaders(headers);
    }
    if (contextType === 'rpc') {
      const ctx = context.switchToRpc();
      const data = ctx.getData<TracingContext>();
      if (isTracingContext(data)) {
        const { payload, tracing } = data;
        context.getArgs()[0] = payload;
        tracingData.parent = tracing;
        parentSpanContext = this.tracerProvider.extractTracing(tracing);
      }
      ctx.getContext<TracingObject>().tracing = tracingData;
      if (typeof ctx.getContext().getPattern === 'function') {
        const pattern = ctx.getContext().getPattern();
        spanTags.set('rpc.pattern', pattern);
      }
      if (typeof ctx.getContext().getChannel === 'function') {
        const channel = ctx.getContext().getChannel();
        spanTags.set('rpc.channel', channel);
      }
      if (typeof ctx.getContext().getSubject === 'function') {
        const subject = ctx.getContext().getSubject();
        spanTags.set('rpc.subject', subject);
      }
      if (typeof ctx.getContext().getTopic === 'function') {
        const topic = ctx.getContext().getTopic();
        spanTags.set('rpc.topic', topic);
      }
      spanLogs.push({ payload: JSON.stringify(ctx.getData(), null, 2) });
    }
    return this.asyncContext.run(() => {
      const span = this.tracer.startSpan(operation, {
        childOf: parentSpanContext,
        tags: { kind: 'server' },
      });
      spanTags.forEach((value: string, key: string) => {
        span.setTag(key, value);
      });
      spanLogs.forEach((log) => {
        span.log(log);
      });
      tracingData.carrier = this.tracerProvider.getCarrier(span);
      this.asyncContext.set(TRACING_CARRIER_INFO, tracingData);
      return next.handle().pipe(
        tap(() => {
          span.finish();
        }),
        catchError((error: Error) => {
          span.setTag(Tags.ERROR, true);
          if (error instanceof HttpException) {
            const tag =
              contextType === 'http'
                ? Tags.HTTP_STATUS_CODE
                : 'error.status_code';
            span.setTag(tag, error.getStatus());
          }
          span.log({
            event: Tags.ERROR,
            'error.object': error,
            message: error.message,
            stack: error.stack,
          });
          span.finish();
          return throwError(error);
        }),
      );
    }) as Observable<unknown>;
  }
Example #27
Source File: resolve-additional-resolvers.ts    From graphql-mesh with MIT License 4 votes vote down vote up
export function resolveAdditionalResolvers(
  baseDir: string,
  additionalResolvers: (
    | string
    | YamlConfig.AdditionalStitchingResolverObject
    | YamlConfig.AdditionalSubscriptionObject
    | YamlConfig.AdditionalStitchingBatchResolverObject
  )[],
  importFn: ImportFn,
  pubsub: MeshPubSub
): Promise<IResolvers[]> {
  return Promise.all(
    (additionalResolvers || []).map(async additionalResolver => {
      if (typeof additionalResolver === 'string') {
        const resolvers = await loadFromModuleExportExpression<any>(additionalResolver, {
          cwd: baseDir,
          defaultExportName: 'resolvers',
          importFn,
        });

        if (!resolvers) {
          console.warn(`Unable to load resolvers from file: ${additionalResolver}`);

          return {};
        }

        return resolvers;
      } else {
        const baseOptions: any = {};
        if (additionalResolver.result) {
          baseOptions.valuesFromResults = generateValuesFromResults(additionalResolver.result);
        }
        if ('pubsubTopic' in additionalResolver) {
          return {
            [additionalResolver.targetTypeName]: {
              [additionalResolver.targetFieldName]: {
                subscribe: withFilter(
                  (root, args, context, info) => {
                    const resolverData = { root, args, context, info, env: process.env };
                    const topic = stringInterpolator.parse(additionalResolver.pubsubTopic, resolverData);
                    return pubsub.asyncIterator(topic) as AsyncIterableIterator<any>;
                  },
                  (root, args, context, info) => {
                    // eslint-disable-next-line no-new-func
                    return additionalResolver.filterBy ? new Function(`return ${additionalResolver.filterBy}`)() : true;
                  }
                ),
                resolve: (payload: any) => {
                  if (baseOptions.valuesFromResults) {
                    return baseOptions.valuesFromResults(payload);
                  }
                  return payload;
                },
              },
            },
          };
        } else if ('keysArg' in additionalResolver) {
          return {
            [additionalResolver.targetTypeName]: {
              [additionalResolver.targetFieldName]: {
                selectionSet: additionalResolver.requiredSelectionSet || `{ ${additionalResolver.keyField} }`,
                resolve: async (root: any, args: any, context: any, info: any) => {
                  if (!baseOptions.selectionSet) {
                    baseOptions.selectionSet = generateSelectionSetFactory(info.schema, additionalResolver);
                  }
                  const resolverData = { root, args, context, info, env: process.env };
                  const targetArgs: any = {};
                  for (const argPath in additionalResolver.additionalArgs || {}) {
                    lodashSet(
                      targetArgs,
                      argPath,
                      stringInterpolator.parse(additionalResolver.additionalArgs[argPath], resolverData)
                    );
                  }
                  const options: any = {
                    ...baseOptions,
                    root,
                    context,
                    info,
                    argsFromKeys: (keys: string[]) => {
                      const args: any = {};
                      lodashSet(args, additionalResolver.keysArg, keys);
                      Object.assign(args, targetArgs);
                      return args;
                    },
                    key: lodashGet(root, additionalResolver.keyField),
                  };
                  return context[additionalResolver.sourceName][additionalResolver.sourceTypeName][
                    additionalResolver.sourceFieldName
                  ](options);
                },
              },
            },
          };
        } else if ('targetTypeName' in additionalResolver) {
          return {
            [additionalResolver.targetTypeName]: {
              [additionalResolver.targetFieldName]: {
                selectionSet: additionalResolver.requiredSelectionSet,
                resolve: (root: any, args: any, context: any, info: GraphQLResolveInfo) => {
                  // Assert source exists
                  if (!context[additionalResolver.sourceName]) {
                    throw new Error(`No source found named "${additionalResolver.sourceName}"`);
                  }
                  if (!context[additionalResolver.sourceName][additionalResolver.sourceTypeName]) {
                    throw new Error(
                      `No root type found named "${additionalResolver.sourceTypeName}" exists in the source ${additionalResolver.sourceName}\n` +
                        `It should be one of the following; ${Object.keys(context[additionalResolver.sourceName]).join(
                          ','
                        )})}}`
                    );
                  }
                  if (
                    !context[additionalResolver.sourceName][additionalResolver.sourceTypeName][
                      additionalResolver.sourceFieldName
                    ]
                  ) {
                    throw new Error(
                      `No field named "${additionalResolver.sourceFieldName}" exists in the type ${additionalResolver.sourceTypeName} from the source ${additionalResolver.sourceName}`
                    );
                  }

                  if (!baseOptions.selectionSet) {
                    baseOptions.selectionSet = generateSelectionSetFactory(info.schema, additionalResolver);
                  }
                  const resolverData = { root, args, context, info, env: process.env };
                  const targetArgs: any = {};
                  for (const argPath in additionalResolver.sourceArgs) {
                    lodashSet(
                      targetArgs,
                      argPath,
                      stringInterpolator.parse(additionalResolver.sourceArgs[argPath].toString(), resolverData)
                    );
                  }
                  const options: any = {
                    ...baseOptions,
                    root,
                    args: targetArgs,
                    context,
                    info,
                  };
                  return context[additionalResolver.sourceName][additionalResolver.sourceTypeName][
                    additionalResolver.sourceFieldName
                  ](options);
                },
              },
            },
          };
        } else {
          return additionalResolver;
        }
      }
    })
  );
}
Example #28
Source File: snapshot.spec.ts    From graphql-mesh with MIT License 4 votes vote down vote up
describe('snapshot', () => {
  const baseDir: string = undefined;
  const outputDir = join(tmpdir(), '__snapshots__');
  const users = [
    {
      id: '0',
      name: 'Uri Goldshtein',
      age: 20,
      email: '[email protected]',
      address: 'Earth',
    },
    {
      id: '1',
      name: 'Dotan Simha',
      age: 19,
      email: '[email protected]',
      address: 'Moon',
    },
  ];
  let pubsub: MeshPubSub;
  beforeEach(async () => {
    pubsub = new PubSub();
    await mkdir(outputDir);
  });
  afterEach(async () => {
    await rmdirs(outputDir);
  });
  it('it writes correct output', async () => {
    const schema = wrapSchema({
      schema: makeExecutableSchema({
        typeDefs: /* GraphQL */ `
          type Query {
            user(id: ID): User
          }
          type User {
            id: ID
            name: String
            age: Int
            email: String
            address: String
          }
        `,
        resolvers: {
          Query: {
            user: (_, args) => users.find(user => args.id === user.id),
          },
        },
      }),
      transforms: [
        new SnapshotTransform({
          apiName: '',
          importFn: m => import(m),
          config: {
            apply: ['Query.user'],
            outputDir,
          },
          cache: new InMemoryLRUCache(),
          pubsub,
          baseDir,
        }),
      ],
    });

    await graphql({
      schema,
      source: /* GraphQL */ `
        {
          user(id: "0") {
            id
            name
            age
            email
            address
          }
        }
      `,
    });

    const fileName = computeSnapshotFilePath({
      info: {
        parentType: {
          name: 'Query',
        },
        fieldName: 'user',
      } as GraphQLResolveInfo,
      args: { id: '0' },
      outputDir,
    });

    expect(await import(fileName)).toMatchObject(users[0]);
  });

  it('should not call again if there is snapshot created', async () => {
    let calledCounter = 0;
    const schema = wrapSchema({
      schema: makeExecutableSchema({
        typeDefs: /* GraphQL */ `
          type Query {
            user(id: ID): User
          }
          type User {
            id: ID
            name: String
            age: Int
            email: String
            address: String
          }
        `,
        resolvers: {
          Query: {
            user: (_, args) => {
              calledCounter++;
              return users.find(user => args.id === user.id);
            },
          },
        },
      }),
      transforms: [
        new SnapshotTransform({
          apiName: '',
          importFn: m => import(m),
          config: {
            apply: ['Query.user'],
            outputDir,
          },
          cache: new InMemoryLRUCache(),
          pubsub,
          baseDir,
        }),
      ],
    });

    const doTheRequest = () =>
      graphql({
        schema,
        source: /* GraphQL */ `
          {
            user(id: "1") {
              id
              name
              age
              email
              address
            }
          }
        `,
      });
    await doTheRequest();
    expect(calledCounter).toBe(1);
    await doTheRequest();
    expect(calledCounter).toBe(1);
  });
  it('should respect selection set if respectSelectionSet is true', async () => {
    let calledCounter = 0;
    const schema = wrapSchema({
      schema: makeExecutableSchema({
        typeDefs: /* GraphQL */ `
          type Query {
            user(id: ID): User
          }
          type User {
            id: ID
            name: String
            age: Int
            email: String
            address: String
          }
        `,
        resolvers: {
          Query: {
            user: (_, args, info) => {
              calledCounter++;
              // filter results by selection set to project user object
              // this mimics SQL handler behaviors
              const fieldMap: Record<string, any> = graphqlFields(info);
              const fields = Object.keys(fieldMap).filter(fieldName => Object.keys(fieldMap[fieldName]).length === 0);
              const foundUser = users.find(user => args.id === user.id);
              return foundUser ?? _.pick(foundUser, ...fields);
            },
          },
        },
      }),
      transforms: [
        new SnapshotTransform({
          apiName: '',
          importFn: m => import(m),
          config: {
            apply: ['Query.user'],
            outputDir,
            respectSelectionSet: true,
          },
          cache: new InMemoryLRUCache(),
          pubsub,
          baseDir,
        }),
      ],
    });

    const doTheRequest = () =>
      graphql({
        schema,
        source: /* GraphQL */ `
          {
            user(id: "1") {
              id
              name
              age
              email
              address
            }
          }
        `,
      });
    await doTheRequest();
    expect(calledCounter).toBe(1);
    await doTheRequest();
    expect(calledCounter).toBe(1);

    const doTheSecondRequest = () =>
      graphql({
        schema,
        source: /* GraphQL */ `
          {
            user(id: "1") {
              id
              name
            }
          }
        `,
      });
    await doTheSecondRequest();
    expect(calledCounter).toBe(2);
    await doTheSecondRequest();
    expect(calledCounter).toBe(2);
  });
});
Example #29
Source File: index.ts    From graphql-mesh with MIT License 4 votes vote down vote up
transformSchema(schema: GraphQLSchema) {
    const configIf = this.config != null && 'if' in this.config ? this.config.if : true;
    if (configIf) {
      const mocks: IMocks = {
        ...graphqlScalarsMocks,
      };
      const resolvers: any = {};
      const typeMap = schema.getTypeMap();
      for (const typeName in typeMap) {
        const type = typeMap[typeName];
        const examples = type.extensions.examples as any[];
        if (examples?.length) {
          mocks[typeName] = () => examples[Math.floor(Math.random() * examples.length)];
        }
      }
      if (this.config?.mocks?.length) {
        for (const fieldConfig of this.config.mocks) {
          const fieldConfigIf = 'if' in fieldConfig ? fieldConfig.if : true;
          if (fieldConfigIf) {
            const [typeName, fieldName] = fieldConfig.apply.split('.');
            if (fieldName) {
              if (fieldConfig.faker) {
                let fakerFn: Function; // eslint-disable-line
                const [service, method] = fieldConfig.faker.split('.');
                if (service in faker) {
                  fakerFn = () => (faker as any)[service][method]();
                } else {
                  fakerFn = () => faker.fake(fieldConfig.faker || '');
                }
                resolvers[typeName] = resolvers[typeName] || {};
                resolvers[typeName][fieldName] = fakerFn;
              } else if (fieldConfig.custom) {
                const exportedVal$ = loadFromModuleExportExpression<any>(fieldConfig.custom, {
                  cwd: this.baseDir,
                  defaultExportName: 'default',
                  importFn: this.importFn,
                });
                resolvers[typeName] = resolvers[typeName] || {};
                resolvers[typeName][fieldName] = (root: any, args: any, context: any, info: GraphQLResolveInfo) => {
                  context = context || {};
                  context.mockStore = store;
                  return exportedVal$.then(exportedVal =>
                    typeof exportedVal === 'function' ? exportedVal(root, args, context, info) : exportedVal
                  );
                };
              } else if ('length' in fieldConfig) {
                resolvers[typeName] = resolvers[typeName] || {};
                resolvers[typeName][fieldName] = () => new Array(fieldConfig.length).fill({});
              } else if ('updateStore' in fieldConfig) {
                const getFromStoreKeyFactory = getInterpolatedStringFactory(fieldConfig.store.key);
                const updateStoreFactories = fieldConfig.updateStore.map(updateStoreConfig => ({
                  updateStoreConfig,
                  keyFactory: getInterpolatedStringFactory(updateStoreConfig.key),
                  valueFactory: getInterpolatedStringFactory(updateStoreConfig.value),
                }));
                resolvers[typeName] = resolvers[typeName] || {};
                resolvers[typeName][fieldName] = (root: any, args: any, context: any, info: GraphQLResolveInfo) => {
                  const resolverData = { root, args, context, info, random: Date.now().toString(), env: process.env };
                  updateStoreFactories.forEach(({ updateStoreConfig, keyFactory, valueFactory }) => {
                    const key = keyFactory(resolverData);
                    const value = valueFactory(resolverData);
                    store.set(updateStoreConfig.type, key, updateStoreConfig.fieldName, value);
                  });
                  const key = getFromStoreKeyFactory(resolverData);
                  return store.get(fieldConfig.store.type, key, fieldConfig.store.fieldName);
                };
              } else if ('store' in fieldConfig) {
                const keyFactory = getInterpolatedStringFactory(fieldConfig.store.key);
                resolvers[typeName] = resolvers[typeName] || {};
                resolvers[typeName][fieldName] = (root: any, args: any, context: any, info: GraphQLResolveInfo) => {
                  const key = keyFactory({ root, args, context, info, env: process.env });
                  return store.get(fieldConfig.store.type, key, fieldConfig.store.fieldName);
                };
              }
            } else {
              if (fieldConfig.faker) {
                let fakerFn: GraphQLFieldResolver<any, any, { [argName: string]: any }>;
                const [service, method] = fieldConfig.faker.split('.');
                if (service in faker) {
                  fakerFn = () => (faker as any)[service][method]();
                } else {
                  fakerFn = () => faker.fake(fieldConfig.faker || '');
                }
                mocks[typeName] = fakerFn;
              } else if (fieldConfig.custom) {
                const exportedVal$ = loadFromModuleExportExpression<any>(fieldConfig.custom, {
                  cwd: this.baseDir,
                  defaultExportName: 'default',
                  importFn: this.importFn,
                });
                mocks[typeName] = () => {
                  return exportedVal$.then(exportedVal =>
                    typeof exportedVal === 'function' ? exportedVal(store) : exportedVal
                  );
                };
              }
            }
          }
        }
      }
      const store = createMockStore({ schema, mocks });
      if (this.config?.initializeStore) {
        const initializeStoreFn$ = loadFromModuleExportExpression(this.config.initializeStore, {
          cwd: this.baseDir,
          defaultExportName: 'default',
          importFn: this.importFn,
        });
        // eslint-disable-next-line no-void
        void initializeStoreFn$.then(fn => fn(store));
      }
      return addMocksToSchema({
        schema,
        store,
        mocks,
        resolvers,
        preserveResolvers: this.config?.preserveResolvers,
      });
    }
    return schema;
  }