import * as GraphQL from "graphql" ; import { FieldNodeWithSelectionSet , GetObjectId , RootFields , Variables , ResponseObject , ResolveType } from "./types" ; import { defaultGetObjectId , expandFragments , getDocumentDefinitions , fieldNameWithArguments , shouldIncludeField , defaultResolveType } from "./functions" ; import { NormMap , NormObj , NormKey , NormFieldValue } from "./norm-map" ; type MutableDeep < T > = { - readonly [ P in keyof T ] : MutableDeep < T [ P ] > ; } ; type ParentNormObj = MutableDeep < NormObj > ; type MutableNormMap = MutableDeep < NormMap > ; type ResponseArray = ReadonlyArray < ResponseObject | ReadonlyArray < ResponseObject > > ; type ResponseObjectOrArray = ResponseObject | ResponseArray ; type ParentNormObjOrArray = ParentNormObj | ParentArray ; type ParentArray = Array < NormFieldValue > ; type StackWorkItem = readonly [ FieldNodeWithSelectionSet , ParentNormObjOrArray | undefined , ResponseObjectOrArray , string ] ; param query param variables param response param getObjectId param resolveType export function normalize ( query , variables , data , getObjectId = defaultGetObjectId , resolveType = defaultResolveType ) { const [ fragmentMap , rootFieldNode ] = getDocumentDefinitions ( query . definitions ) ; const stack = [ ] ; const normMap = Object . create ( null ) ; stack . push ( [ rootFieldNode , Object . create ( null ) , data , "ROOT_QUERY" ] ) ; let getObjectIdToUse = _ => "ROOT_QUERY" ; let firstIteration = true ; while ( stack . length > 0 ) { const [ fieldNode , parentNormObjOrArray , responseObjectOrArray , fallbackId ] = stack . pop ( ) ! ; let keyOrNewParentArray = null ; if ( responseObjectOrArray === null ) { keyOrNewParentArray = null ; } else if ( ! Array . isArray ( responseObjectOrArray ) ) { const responseObject = responseObjectOrArray as ResponseObject ; const objectToIdResult = getObjectIdToUse ( responseObject ) ; keyOrNewParentArray = objectToIdResult ? objectToIdResult : fallbackId ; let normObj = normMap [ keyOrNewParentArray ] ; if ( ! normObj ) { normObj = Object . create ( null ) ; normMap [ keyOrNewParentArray ] = normObj ; } const expandedSelections = expandFragments ( resolveType , responseObjectOrArray , fieldNode . selectionSet . selections , fragmentMap ) ; for ( const field of expandedSelections ) { const include = field . directives ? shouldIncludeField ( field . directives , variables ) : true ; if ( include ) { const responseFieldValue = responseObject [ ( field . alias && field . alias . value ) || field . name . value ] ; const normFieldName = field . arguments && field . arguments . length > 0 ? fieldNameWithArguments ( field , variables ) : field . name . value ; if ( responseFieldValue !== null && field . selectionSet ) { stack . push ( [ field as FieldNodeWithSelectionSet , normObj , responseFieldValue , keyOrNewParentArray + "." + normFieldName ] ) ; } else { normObj [ normFieldName ] = responseFieldValue ; } } } } else { const responseArray = responseObjectOrArray as ResponseArray ; keyOrNewParentArray = [ ] ; for ( let i = 0 ; i < responseArray . length ; i ++ ) { stack . push ( [ fieldNode , keyOrNewParentArray , responseArray [ i ] , fallbackId + "." + i . toString ( ) ] ) ; } } if ( Array . isArray ( parentNormObjOrArray ) ) { const parentArray = parentNormObjOrArray as ParentArray ; parentArray . unshift ( keyOrNewParentArray ) ; } else { const key = fieldNode . arguments && fieldNode . arguments . length > 0 ? fieldNameWithArguments ( fieldNode , variables ) : fieldNode . name . value ; const parentNormObj = parentNormObjOrArray as ParentNormObj ; parentNormObj [ key ] = keyOrNewParentArray ; } if ( firstIteration ) { getObjectIdToUse = getObjectId ; firstIteration = false ; } } return normMap as NormMap ; }