import { OperationDefinitionNode , visit , GraphQLSchema , FieldNode , BREAK } from 'graphql' ; import { QueryMap , QueryMapElem } from './query-map' ; import { QueryTreeHandler , QueryNodePurgeHandler } from './query-tree-handler' ; import { QueryTreeNode } from './query-tree-node' ; import { VariableStore , Variable } from '../var-store' ; import { rgraphql , PackPrimitive } from 'rgraphql' ; import { Query } from './query' ; import { AttachedQuery } from './query-attached' ; import { getLookupType } from '../util' ; export class QueryTree { private nextID = 1 ; private nextQueryID = 1 ; private attachedQueries : { [ queryID ] : AttachedQuery ; } = { } ; private root : QueryTreeNode ; private varStore : VariableStore ; private pendingVariables : rgraphql . IASTVariable [ ] = [ ] ; private handlers : QueryTreeHandler [ ] ; private qtNodePurgeHandlers : QueryNodePurgeHandler [ ] = [ ] ; constructor ( private schema , handler ? ) { let queryType = schema . getQueryType ( ) ; if ( ! queryType ) { throw new Error ( 'schema has no query type definedj' ) ; } this . varStore = new VariableStore ( this . handleNewVariable . bind ( this ) ) ; this . root = new QueryTreeNode ( 0 , '' , queryType , this . varStore ) ; this . handlers = [ ] ; if ( handler ) { this . handlers . push ( handler ) ; } } public getGraphQLSchema ( ) { return this . schema ; } public getRoot ( ) { return this . root ; } public buildQuery ( ast , variables ? ) { let nqid = this . nextQueryID ; this . nextQueryID ++ ; let query = new Query ( nqid , ast , variables || null ) ; this . attach ( query ) ; return query ; } public attach ( query ) { if ( this . attachedQueries . hasOwnProperty ( query . getQueryID ( ) ) ) { return ; } let qtree = this ; let lookupType = getLookupType ( this . schema ) ; let varStore = this . varStore ; let qnode = this . root ; let validateErr = null ; let attachedQuery = new AttachedQuery ( query ) ; let newNodes = [ ] ; let newNodeDepth = 0 ; let qmap = { } ; let qmapStack = [ qmap ] ; visit ( query . ast , { Field : { enter ( node ) { if ( ! node . name || ! node . name . value || ! node . name . value . length ) { return false ; } let childNode = null ; try { childNode = qnode . resolveChild ( node , lookupType , ( ) => { let nodeID = qtree . nextID ; qtree . nextID ++ ; return new QueryTreeNode ( nodeID , '' , null , varStore ) ; } ) ; } catch ( e ) { validateErr = e ; return BREAK ; } if ( ! childNode ) { return BREAK ; } if ( childNode . getRefCount ( ) === 0 && newNodeDepth === 0 ) { newNodes . push ( childNode ) ; newNodeDepth ++ ; } else if ( newNodeDepth ) { newNodeDepth ++ ; } childNode . incRefCount ( ) ; attachedQuery . appendQueryNode ( childNode ) ; qnode = childNode ; let qme = qmapStack [ qmapStack . length - 1 ] ; let elem = { } ; if ( node . selectionSet && node . selectionSet . selections . length ) { let childQm = { } ; elem . selections = childQm ; qmapStack . push ( childQm ) ; } if ( node . alias && node . alias . value ) { elem . alias = node . alias . value ; } qme [ qnode . getID ( ) ] = elem ; return ; } , leave ( node ) { if ( ! node . name || ! node . name . value || ! node . name . value . length ) { return false ; } if ( node . selectionSet && node . selectionSet . selections . length ) { qmapStack . length -= 1 ; } if ( newNodeDepth ) { newNodeDepth -- ; } let parent = qnode . getParent ( ) ; if ( ! parent ) { throw new Error ( 'expected parent but found none' ) ; } qnode = parent ; return false ; } } } ) ; if ( validateErr ) { for ( let qn of attachedQuery . qtNodes ) { if ( qn . decRefCount ( ) === 0 ) { qn . flagGcNext ( ) ; } } this . gcSweep ( ) ; this . pendingVariables = [ ] ; throw validateErr ; } if ( newNodes . length && this . handlers . length ) { let nodeMutation = [ ] ; for ( let n of newNodes ) { n . markXmitted ( ) ; let parent = n . getParent ( ) ; if ( ! parent ) { continue ; } nodeMutation . push ( { nodeId : parent . getID ( ) , node : n . buildProto ( ) , operation : rgraphql . RGQLQueryTreeMutation . SubtreeOperation . SUBTREE_ADD_CHILD } ) ; } this . emitToHandlers ( { nodeMutation , variables : this . pendingVariables } ) ; } this . pendingVariables = [ ] ; this . attachedQueries [ query . getQueryID ( ) ] = attachedQuery ; this . gcSweep ( ) ; query . setQueryMap ( qmap ) ; } public detach ( query ) { let attachedQuery = this . attachedQueries [ query . getQueryID ( ) ] ; if ( ! attachedQuery ) { return ; } let gcSweep = false ; for ( let nod of attachedQuery . qtNodes ) { if ( nod . decRefCount ( ) === 0 ) { gcSweep = true ; nod . flagGcNext ( ) ; } } delete this . attachedQueries [ query . getQueryID ( ) ] ; if ( gcSweep ) { this . gcSweep ( ) ; } } public attachHandler ( handler ) { this . handlers . push ( handler ) ; } public removeHandler ( handler ) { for ( let i = 0 ; i < this . handlers . length ; i ++ ) { if ( this . handlers [ i ] === handler ) { this . handlers [ i ] = this . handlers [ this . handlers . length - 1 ] ; this . handlers . splice ( this . handlers . length - 1 , 1 ) ; break ; } } } public attachQtNodePurgeHandler ( handler ) { this . qtNodePurgeHandlers . push ( handler ) ; } public detachQtNodePurgeHandler ( handler ) { for ( let i = 0 ; i < this . qtNodePurgeHandlers . length ; i ++ ) { if ( this . qtNodePurgeHandlers [ i ] === handler ) { this . qtNodePurgeHandlers [ i ] = this . qtNodePurgeHandlers [ this . qtNodePurgeHandlers . length - 1 ] ; this . qtNodePurgeHandlers . splice ( this . qtNodePurgeHandlers . length - 1 , 1 ) ; break ; } } } public buildProto ( ) { return this . root . buildProto ( ) ; } public gcSweep ( ) { let allUnrefNodes = [ ] ; this . root . gcSweep ( ( unrefNodes ) => { if ( ! unrefNodes || ! unrefNodes . length ) { return ; } allUnrefNodes = allUnrefNodes . concat ( unrefNodes ) ; } ) ; if ( allUnrefNodes . length ) { let muts = [ ] ; for ( let n of allUnrefNodes ) { muts . push ( { nodeId : n . getID ( ) , operation : rgraphql . RGQLQueryTreeMutation . SubtreeOperation . SUBTREE_DELETE } ) ; } this . emitToHandlers ( { nodeMutation : muts } ) ; this . emitToPurgeHandlers ( allUnrefNodes ) ; } } private emitToHandlers ( mut ) { for ( let handler of this . handlers ) { handler ( mut ) ; } } private emitToPurgeHandlers ( nodes ) { for ( let purgeHandler of this . qtNodePurgeHandlers ) { for ( let node of nodes ) { purgeHandler ( node ) ; } } } private handleNewVariable ( varb ) { this . pendingVariables . push ( varb . toProto ( ) ) ; } }