import { FieldNode , FieldDefinitionNode , ArgumentNode , GraphQLObjectType } from 'graphql' ; import { ArgsMap , ArgsToProto , CompareArgs , NewArgsMapFromAST } from './args-map' ; import { VariableStore } from '../var-store' ; import { rgraphql } from 'rgraphql' ; import { LookupASTType } from '../util/type-lookup' ; import { FieldNotFoundError , TypeNotFoundError } from './errors' ; import { isAstPrimitive , unwrapAstType } from '../util' ; export class QueryTreeNode { private children : QueryTreeNode [ ] = [ ] ; private args : ArgsMap | null = null ; private parent : QueryTreeNode | null = null ; private refCount : number = 0 ; private gcNext : boolean | undefined ; private xmitted : boolean | undefined ; constructor ( private id , private name , private objDef , private varStore ) { } public buildProto ( ) { let children = [ ] ; for ( let child of this . children ) { children . push ( child . buildProto ( ) ) ; } return { id : this . id , fieldName : this . name , args : ArgsToProto ( this . args ) , children , directive : [ ] } ; } public flagGcNext ( ) { this . gcNext = true ; if ( this . parent ) { this . parent . flagGcNext ( ) ; } } public matchesField ( fieldName , args ) { if ( fieldName !== this . name ) { return false ; } return CompareArgs ( args , this . args ) ; } public lookupChildByID ( qnID ) { for ( let child of this . children ) { if ( child . id === qnID ) { return child ; } } return null ; } public getID ( ) { return this . id ; } public getName ( ) { return this . name || '' ; } public getParent ( ) { return this . parent ; } public getRefCount ( ) { return this . refCount ; } public incRefCount ( ) { this . refCount ++ ; return this . refCount ; } public decRefCount ( ) { if ( this . refCount > 0 ) { this . refCount -- ; } return this . refCount ; } public resolveChild ( field , lookupType , allocNode ) { let fieldName = field . name . value ; let childFieldDef = this . findSchemaField ( fieldName ) ; if ( ! childFieldDef ) { throw new FieldNotFoundError ( `field ${ fieldName } not found` ) ; } let atdObj = null ; let childFieldType = childFieldDef . type ; let childFieldTypeUnderlying = unwrapAstType ( childFieldType ) ; if ( ! isAstPrimitive ( childFieldTypeUnderlying ) ) { let atd = lookupType ( childFieldTypeUnderlying ) ; if ( atd ) { let atdv = atd as any ; if ( atdv . astNode && atdv . astNode . kind === 'ObjectTypeDefinition' ) { atdObj = atd as GraphQLObjectType ; } } if ( ! atdObj ) { if ( childFieldType . kind === 'NamedType' ) { throw new TypeNotFoundError ( 'type ' + childFieldType . name . value + ' not found' ) ; } throw new TypeNotFoundError ( '[unknown reference type]' ) ; } } let argsm = NewArgsMapFromAST ( this . varStore , field . arguments as ArgumentNode [ ] ) ; for ( let child of this . children ) { if ( child . matchesField ( field . name . value , argsm ) ) { return child ; } } let childNod = allocNode ( ) ; childNod . varStore = this . varStore ; childNod . args = argsm ; childNod . name = fieldName ; childNod . objDef = atdObj ; childNod . parent = this ; this . children . push ( childNod ) ; return childNod ; } public findSchemaField ( fieldName ) { if ( ! this . objDef || ! this . objDef . astNode ) { return null ; } let fields = this . objDef . astNode . fields ; if ( ! this . objDef || ! fields ) { return null ; } for ( let field of fields ) { if ( ! field . name || ! field . name . value || ! field . name . value . length ) { continue ; } if ( fieldName === field . name . value ) { return field ; } } return null ; } public gcSweep ( purgeNodesCb ) { let unrefChildren = [ ] ; if ( this . gcNext ) { this . gcNext = false ; for ( let i = 0 ; i < this . children . length ; i ++ ) { let child = this . children [ i ] ; if ( ! child . gcSweep ( purgeNodesCb ) ) { this . children [ i ] = this . children [ this . children . length - 1 ] ; this . children . splice ( this . children . length - 1 , 1 ) ; if ( child . xmitted ) { unrefChildren . push ( child ) ; } i -- ; } } } let qReferenced = this . refCount !== 0 || ! this . parent ; if ( unrefChildren . length !== 0 && qReferenced ) { purgeNodesCb ( unrefChildren ) ; } return qReferenced ; } public markXmitted ( ) { if ( this . xmitted ) { return ; } this . xmitted = true ; for ( let child of this . children ) { child . markXmitted ( ) ; } } }