import { Kind , SelectionNode as GraphQLJSSelectionNode , } from 'graphql' ; import prettyFormat from 'pretty-format' ; import { queryPlanSerializer , astSerializer } from './snapshotSerializers' ; export type ResponsePath = ( string | number ) [ ] ; export interface QueryPlan { kind : 'QueryPlan' ; node ? : PlanNode ; } export type PlanNode = SequenceNode | ParallelNode | FetchNode | FlattenNode ; export interface SequenceNode { kind : 'Sequence' ; nodes : PlanNode [ ] ; } export interface ParallelNode { kind : 'Parallel' ; nodes : PlanNode [ ] ; } export interface FetchNode { kind : 'Fetch' ; serviceName : string ; variableUsages ? : string [ ] ; requires ? : QueryPlanSelectionNode [ ] ; operation : string ; } export interface FlattenNode { kind : 'Flatten' ; path : ResponsePath ; node : PlanNode ; } /**
 * SelectionNodes from GraphQL-js _can_ have a FragmentSpreadNode
 * but this SelectionNode is specifically typing the `requires` key
 * in a built query plan, where there can't be FragmentSpreadNodes
 * since that info is contained in the `FetchNode.operation`
 */ export type QueryPlanSelectionNode = QueryPlanFieldNode | QueryPlanInlineFragmentNode ; export interface QueryPlanFieldNode { readonly kind : 'Field' ; readonly alias ? : string ; readonly name : string ; readonly selections ? : QueryPlanSelectionNode [ ] ; } export interface QueryPlanInlineFragmentNode { readonly kind : 'InlineFragment' ; readonly typeCondition ? : string ; readonly selections : QueryPlanSelectionNode [ ] ; } export function serializeQueryPlan ( queryPlan ) { return prettyFormat ( queryPlan , { plugins : [ queryPlanSerializer , astSerializer ] , } ) ; } export function getResponseName ( node ) { return node . alias ? node . alias : node . name ; } /**
 * Converts a GraphQL-js SelectionNode to our newly defined SelectionNode
 *
 * This function is used to remove the unneeded pieces of a SelectionSet's
 * `.selections`. It is only ever called on a query plan's `requires` field,
 * so we can guarantee there won't be any FragmentSpreads passed in. That's why
 * we can ignore the case where `selection.kind === Kind.FRAGMENT_SPREAD`
 */ export const trimSelectionNodes = ( selections ) : QueryPlanSelectionNode [ ] => { /**
     * Using an array to push to instead of returning value from `selections.map`
     * because TypeScript thinks we can encounter a `Kind.FRAGMENT_SPREAD` here,
     * so if we mapped the array directly to the return, we'd have to `return undefined`
     * from one branch of the map and then `.filter(Boolean)` on that returned
     * array
     */ const remapped = [ ] ; selections . forEach ( ( selection ) => { if ( selection . kind === Kind . FIELD ) { remapped . push ( { kind : Kind . FIELD , name : selection . name . value , selections : selection . selectionSet && trimSelectionNodes ( selection . selectionSet . selections ) , } ) ; } if ( selection . kind === Kind . INLINE_FRAGMENT ) { remapped . push ( { kind : Kind . INLINE_FRAGMENT , typeCondition : selection . typeCondition ?. name . value , selections : trimSelectionNodes ( selection . selectionSet . selections ) , } ) ; } } ) ; return remapped ; } ;