{"version":3,"sources":["../../../src/core/handlers/GraphQLHandler.ts"],"sourcesContent":["import { invariant } from 'outvariant'\nimport {\n  parse,\n  type DocumentNode,\n  type GraphQLError,\n  type OperationTypeNode,\n} from 'graphql'\nimport {\n  DefaultBodyType,\n  RequestHandler,\n  RequestHandlerDefaultInfo,\n  RequestHandlerExecutionResult,\n  RequestHandlerOptions,\n  ResponseResolver,\n} from './RequestHandler'\nimport { getTimestamp } from '../utils/logging/getTimestamp'\nimport { getStatusCodeColor } from '../utils/logging/getStatusCodeColor'\nimport { serializeRequest } from '../utils/logging/serializeRequest'\nimport { serializeResponse } from '../utils/logging/serializeResponse'\nimport { Match, matchRequestUrl, Path } from '../utils/matching/matchRequestUrl'\nimport {\n  ParsedGraphQLRequest,\n  GraphQLMultipartRequestBody,\n  parseGraphQLRequest,\n  parseDocumentNode,\n  ParsedGraphQLQuery,\n} from '../utils/internal/parseGraphQLRequest'\nimport { toPublicUrl } from '../utils/request/toPublicUrl'\nimport { devUtils } from '../utils/internal/devUtils'\nimport { getAllRequestCookies } from '../utils/request/getRequestCookies'\nimport { ResponseResolutionContext } from 'src/iife'\nimport { kDefaultContentType, StrictRequest } from '../HttpResponse'\nimport { getAllAcceptedMimeTypes } from '../utils/request/getAllAcceptedMimeTypes'\n\nexport interface DocumentTypeDecoration<\n  Result = { [key: string]: any },\n  Variables = { [key: string]: any },\n> {\n  __apiType?: (variables: Variables) => Result\n  __resultType?: Result\n  __variablesType?: Variables\n}\n\nexport type GraphQLOperationType = OperationTypeNode | 'all'\nexport type GraphQLHandlerNameSelector = DocumentNode | RegExp | string\n\nexport type GraphQLQuery = Record<string, any> | null\nexport type GraphQLVariables = Record<string, any>\n\nexport interface GraphQLHandlerInfo extends RequestHandlerDefaultInfo {\n  operationType: GraphQLOperationType\n  operationName: GraphQLHandlerNameSelector | GraphQLCustomPredicate\n}\n\nexport type GraphQLRequestParsedResult = {\n  match: Match\n  cookies: Record<string, string>\n} & (\n  | ParsedGraphQLRequest<GraphQLVariables>\n  /**\n   * An empty version of the ParsedGraphQLRequest\n   * which simplifies the return type of the resolver\n   * when the request is to a non-matching endpoint\n   */\n  | {\n      operationType?: undefined\n      operationName?: undefined\n      query?: undefined\n      variables?: undefined\n    }\n)\n\nexport type GraphQLResolverExtras<Variables extends GraphQLVariables> = {\n  query: string\n  operationName: string\n  variables: Variables\n  cookies: Record<string, string>\n}\n\nexport type GraphQLRequestBody<VariablesType extends GraphQLVariables> =\n  | GraphQLJsonRequestBody<VariablesType>\n  | GraphQLMultipartRequestBody\n  | Record<string, any>\n  | undefined\n\nexport interface GraphQLJsonRequestBody<Variables extends GraphQLVariables> {\n  query: string\n  variables?: Variables\n}\n\nexport type GraphQLResponseBody<BodyType extends DefaultBodyType> =\n  | {\n      data?: BodyType | null\n      errors?: readonly Partial<GraphQLError>[] | null\n      extensions?: Record<string, any>\n    }\n  | null\n  | undefined\n\nexport type GraphQLCustomPredicate = (args: {\n  request: Request\n  query: string\n  operationType: GraphQLOperationType\n  operationName: string\n  variables: GraphQLVariables\n  cookies: Record<string, string>\n}) => GraphQLCustomPredicateResult | Promise<GraphQLCustomPredicateResult>\n\nexport type GraphQLCustomPredicateResult = boolean | { matches: boolean }\n\nexport type GraphQLPredicate<Query = any, Variables = any> =\n  | GraphQLHandlerNameSelector\n  | DocumentTypeDecoration<Query, Variables>\n  | GraphQLCustomPredicate\n\nexport function isDocumentNode(\n  value: DocumentNode | any,\n): value is DocumentNode {\n  if (value == null) {\n    return false\n  }\n\n  return typeof value === 'object' && 'kind' in value && 'definitions' in value\n}\n\nfunction isDocumentTypeDecoration(\n  value: unknown,\n): value is DocumentTypeDecoration<any, any> {\n  return value instanceof String\n}\n\nexport class GraphQLHandler extends RequestHandler<\n  GraphQLHandlerInfo,\n  GraphQLRequestParsedResult,\n  GraphQLResolverExtras<any>\n> {\n  private endpoint: Path\n\n  static parsedRequestCache = new WeakMap<\n    Request,\n    ParsedGraphQLRequest<GraphQLVariables>\n  >()\n\n  static #parseOperationName(\n    predicate: GraphQLPredicate,\n    operationType: GraphQLOperationType,\n  ): GraphQLHandlerInfo['operationName'] {\n    const getOperationName = (node: ParsedGraphQLQuery): string => {\n      invariant(\n        node.operationType === operationType,\n        'Failed to create a GraphQL handler: provided a DocumentNode with a mismatched operation type (expected \"%s\" but got \"%s\").',\n        operationType,\n        node.operationType,\n      )\n\n      invariant(\n        node.operationName,\n        'Failed to create a GraphQL handler: provided a DocumentNode without operation name',\n      )\n\n      return node.operationName\n    }\n\n    if (isDocumentNode(predicate)) {\n      return getOperationName(parseDocumentNode(predicate))\n    }\n\n    if (isDocumentTypeDecoration(predicate)) {\n      const documentNode = parse(predicate.toString())\n\n      invariant(\n        isDocumentNode(documentNode),\n        'Failed to create a GraphQL handler: given TypedDocumentString (%s) does not produce a valid DocumentNode',\n        predicate,\n      )\n\n      return getOperationName(parseDocumentNode(documentNode))\n    }\n\n    return predicate\n  }\n\n  constructor(\n    operationType: GraphQLOperationType,\n    predicate: GraphQLPredicate,\n    endpoint: Path,\n    resolver: ResponseResolver<GraphQLResolverExtras<any>, any, any>,\n    options?: RequestHandlerOptions,\n  ) {\n    const operationName = GraphQLHandler.#parseOperationName(\n      predicate,\n      operationType,\n    )\n\n    const displayOperationName =\n      typeof operationName === 'function' ? '[custom predicate]' : operationName\n\n    const header =\n      operationType === 'all'\n        ? `${operationType} (origin: ${endpoint.toString()})`\n        : `${operationType}${displayOperationName ? ` ${displayOperationName}` : ''} (origin: ${endpoint.toString()})`\n\n    super({\n      info: {\n        header,\n        operationType,\n        operationName: GraphQLHandler.#parseOperationName(\n          predicate,\n          operationType,\n        ),\n      },\n      resolver,\n      options,\n    })\n\n    this.endpoint = endpoint\n  }\n\n  /**\n   * Parses the request body, once per request, cached across all\n   * GraphQL handlers. This is done to avoid multiple parsing of the\n   * request body, which each requires a clone of the request.\n   */\n  async parseGraphQLRequestOrGetFromCache(\n    request: Request,\n  ): Promise<ParsedGraphQLRequest<GraphQLVariables>> {\n    if (!GraphQLHandler.parsedRequestCache.has(request)) {\n      GraphQLHandler.parsedRequestCache.set(\n        request,\n        await parseGraphQLRequest(request).catch((error) => {\n          console.error(error)\n          return undefined\n        }),\n      )\n    }\n\n    return GraphQLHandler.parsedRequestCache.get(request)\n  }\n\n  async parse(args: { request: Request }): Promise<GraphQLRequestParsedResult> {\n    /**\n     * If the request doesn't match a specified endpoint, there's no\n     * need to parse it since there's no case where we would handle this\n     */\n    const match = matchRequestUrl(new URL(args.request.url), this.endpoint)\n    const cookies = getAllRequestCookies(args.request)\n\n    if (!match.matches) {\n      return {\n        match,\n        cookies,\n      }\n    }\n\n    const parsedResult = await this.parseGraphQLRequestOrGetFromCache(\n      args.request,\n    )\n\n    if (typeof parsedResult === 'undefined') {\n      return {\n        match,\n        cookies,\n      }\n    }\n\n    return {\n      match,\n      cookies,\n      query: parsedResult.query,\n      operationType: parsedResult.operationType,\n      operationName: parsedResult.operationName,\n      variables: parsedResult.variables,\n    }\n  }\n\n  async predicate(args: {\n    request: Request\n    parsedResult: GraphQLRequestParsedResult\n  }): Promise<boolean> {\n    if (args.parsedResult.operationType === undefined) {\n      return false\n    }\n\n    if (!args.parsedResult.operationName && this.info.operationType !== 'all') {\n      const publicUrl = toPublicUrl(args.request.url)\n\n      devUtils.warn(`\\\nFailed to intercept a GraphQL request at \"${args.request.method} ${publicUrl}\": anonymous GraphQL operations are not supported.\n\nConsider naming this operation or using \"graphql.operation()\" request handler to intercept GraphQL requests regardless of their operation name/type. Read more: https://mswjs.io/docs/api/graphql/#graphqloperationresolver`)\n      return false\n    }\n\n    const hasMatchingOperationType =\n      this.info.operationType === 'all' ||\n      args.parsedResult.operationType === this.info.operationType\n\n    /**\n     * Check if the operation name matches the outgoing GraphQL request.\n     * @note Unlike the HTTP handler, the custom predicate functions are invoked\n     * during predicate, not parsing, because GraphQL request parsing happens first,\n     * and non-GraphQL requests are filtered out automatically.\n     */\n    const hasMatchingOperationName = await this.matchOperationName({\n      request: args.request,\n      parsedResult: args.parsedResult,\n    })\n\n    return (\n      args.parsedResult.match.matches &&\n      hasMatchingOperationType &&\n      hasMatchingOperationName\n    )\n  }\n\n  public async run(args: {\n    request: StrictRequest<any>\n    requestId: string\n    resolutionContext?: ResponseResolutionContext\n  }): Promise<RequestHandlerExecutionResult<GraphQLRequestParsedResult> | null> {\n    const result = await super.run(args)\n\n    if (result?.response == null) {\n      return result\n    }\n\n    if (!(kDefaultContentType in result.response)) {\n      return result\n    }\n\n    const acceptedMimeTypes = getAllAcceptedMimeTypes(\n      args.request.headers.get('accept'),\n    )\n\n    if (acceptedMimeTypes.length === 0) {\n      return result\n    }\n\n    const graphqlResponseIndex = acceptedMimeTypes.indexOf(\n      'application/graphql-response+json',\n    )\n    const jsonIndex = acceptedMimeTypes.indexOf('application/json')\n\n    /**\n     * Use the \"application/graphql-response+json\" response content type\n     * only when the client accepts it AND prefers it over \"application/json\"\n     * (i.e. it appears earlier in the precedence-sorted list, or \"application/json\"\n     * is not listed at all).\n     * @see https://github.com/graphql/graphql-over-http/blob/4d1df1fb829ec2dd3ecbf3c6aa4025bd356c270d/spec/GraphQLOverHTTP.md#accept\n     */\n    if (\n      graphqlResponseIndex !== -1 &&\n      (jsonIndex === -1 || graphqlResponseIndex <= jsonIndex)\n    ) {\n      result.response.headers.set(\n        'content-type',\n        'application/graphql-response+json',\n      )\n    }\n\n    return result\n  }\n\n  private async matchOperationName(args: {\n    request: Request\n    parsedResult: GraphQLRequestParsedResult\n  }): Promise<boolean> {\n    if (typeof this.info.operationName === 'function') {\n      const customPredicateResult = await this.info.operationName({\n        request: args.request,\n        ...this.extendResolverArgs({\n          request: args.request,\n          parsedResult: args.parsedResult,\n        }),\n      })\n\n      /**\n       * @note Keep the { matches } signature in case we decide to support path parameters\n       * in GraphQL handlers. If that happens, the custom predicate would have to be moved\n       * to the parsing phase, the same as we have for the HttpHandler, and the user will\n       * have a possibility to return parsed path parameters from the custom predicate.\n       */\n      return typeof customPredicateResult === 'boolean'\n        ? customPredicateResult\n        : customPredicateResult.matches\n    }\n\n    if (this.info.operationName instanceof RegExp) {\n      return this.info.operationName.test(args.parsedResult.operationName || '')\n    }\n\n    return args.parsedResult.operationName === this.info.operationName\n  }\n\n  protected extendResolverArgs(args: {\n    request: Request\n    parsedResult: GraphQLRequestParsedResult\n  }) {\n    return {\n      query: args.parsedResult.query || '',\n      operationType: args.parsedResult.operationType!,\n      operationName: args.parsedResult.operationName || '',\n      variables: args.parsedResult.variables || {},\n      cookies: args.parsedResult.cookies,\n    }\n  }\n\n  async log(args: {\n    request: Request\n    response: Response\n    parsedResult: GraphQLRequestParsedResult\n  }) {\n    const loggedRequest = await serializeRequest(args.request)\n    const loggedResponse = await serializeResponse(args.response)\n    const statusColor = getStatusCodeColor(loggedResponse.status)\n    const requestInfo = args.parsedResult.operationName\n      ? `${args.parsedResult.operationType} ${args.parsedResult.operationName}`\n      : `anonymous ${args.parsedResult.operationType}`\n\n    console.groupCollapsed(\n      devUtils.formatMessage(\n        `${getTimestamp()} ${requestInfo} (%c${loggedResponse.status} ${\n          loggedResponse.statusText\n        }%c)`,\n      ),\n      `color:${statusColor}`,\n      'color:inherit',\n    )\n    // eslint-disable-next-line no-console\n    console.log('Request:', loggedRequest)\n    // eslint-disable-next-line no-console\n    console.log('Handler:', this)\n    // eslint-disable-next-line no-console\n    console.log('Response:', loggedResponse)\n    console.groupEnd()\n  }\n}\n"],"mappings":"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","names":[]}