@@ -14,7 +14,7 @@ import {
1414 FragmentSpreadNode ,
1515 InlineFragmentNode ,
1616 assertCompositeType ,
17- GraphQLField ,
17+ GraphQLField , isCompositeType , GraphQLCompositeType ,
1818} from 'graphql' ;
1919import {
2020 GraphQLUnionType ,
@@ -24,22 +24,49 @@ import {
2424 getNamedType ,
2525 GraphQLError
2626} from 'graphql' ;
27+ import { simpleEstimator } from './estimators' ;
2728
28- type ComplexityResolver = ( args : any , complexity : number ) => number ;
29+ /**
30+ * @deprecated Use new complexity resolver
31+ */
32+ type SimpleComplexityEstimator = ( args : any , complexity : number ) => number ;
33+
34+ export type ComplexityEstimatorArgs = {
35+ type : GraphQLCompositeType ,
36+ field : GraphQLField < any , any > ,
37+ args : { [ key : string ] : any } ,
38+ childComplexity : number
39+ }
40+
41+ export type ComplexityEstimator = ( options : ComplexityEstimatorArgs ) => number | void ;
2942
3043type ComplexityGraphQLField < TSource , TContext > = GraphQLField < TSource , TContext > & {
31- complexity ?: ComplexityResolver | number | undefined
44+ complexity ?: SimpleComplexityEstimator | number | undefined
3245}
3346
3447type ComplexityGraphQLFieldMap < TSource , TContext > = {
3548 [ key : string ] : ComplexityGraphQLField < TSource , TContext >
3649}
3750
3851export interface QueryComplexityOptions {
52+ // The maximum allowed query complexity, queries above this threshold will be rejected
3953 maximumComplexity : number ,
54+
55+ // The query variables. This is needed because the variables are not available
56+ // in the visitor of the graphql-js library
4057 variables ?: Object ,
58+
59+ // Optional callback function to retrieve the determined query complexity
60+ // Will be invoked whether the query is rejected or not
61+ // This can be used for logging or to implement rate limiting
4162 onComplete ?: ( complexity : number ) => void ,
42- createError ?: ( max : number , actual : number ) => GraphQLError
63+
64+ // Optional function to create a custom error
65+ createError ?: ( max : number , actual : number ) => GraphQLError ,
66+
67+ // An array of complexity estimators to use if no estimator or value is defined
68+ // in the field configuration
69+ estimators ?: Array < ComplexityEstimator > ;
4370}
4471
4572function queryComplexityMessage ( max : number , actual : number ) : string {
@@ -53,8 +80,8 @@ export default class QueryComplexity {
5380 context : ValidationContext ;
5481 complexity : number ;
5582 options : QueryComplexityOptions ;
56- fragments : { [ name : string ] : FragmentDefinitionNode } ;
5783 OperationDefinition : Object ;
84+ estimators : Array < ComplexityEstimator > ;
5885
5986 constructor (
6087 context : ValidationContext ,
@@ -67,6 +94,9 @@ export default class QueryComplexity {
6794 this . context = context ;
6895 this . complexity = 0 ;
6996 this . options = options ;
97+ this . estimators = options . estimators || [
98+ simpleEstimator ( )
99+ ] ;
70100
71101 this . OperationDefinition = {
72102 enter : this . onOperationDefinitionEnter ,
@@ -135,7 +165,7 @@ export default class QueryComplexity {
135165 const fieldType = getNamedType ( field . type ) ;
136166
137167 // Get arguments
138- let args ;
168+ let args : { [ key : string ] : any } ;
139169 try {
140170 args = getArgumentValues ( field , childNode , this . options . variables || { } ) ;
141171 } catch ( e ) {
@@ -144,11 +174,7 @@ export default class QueryComplexity {
144174
145175 // Check if we have child complexity
146176 let childComplexity = 0 ;
147- if (
148- fieldType instanceof GraphQLObjectType ||
149- fieldType instanceof GraphQLInterfaceType ||
150- fieldType instanceof GraphQLUnionType
151- ) {
177+ if ( isCompositeType ( fieldType ) ) {
152178 childComplexity = this . nodeComplexity ( childNode , fieldType ) ;
153179 }
154180
@@ -158,7 +184,31 @@ export default class QueryComplexity {
158184 } else if ( typeof field . complexity === 'function' ) {
159185 nodeComplexity = field . complexity ( args , childComplexity ) ;
160186 } else {
161- nodeComplexity = this . getDefaultComplexity ( args , childComplexity ) ;
187+ // Run estimators one after another and return first valid complexity
188+ // score
189+ const estimatorArgs : ComplexityEstimatorArgs = {
190+ childComplexity,
191+ args,
192+ field,
193+ type : typeDef
194+ } ;
195+ const validScore = this . estimators . find ( estimator => {
196+ const tmpComplexity = estimator ( estimatorArgs ) ;
197+
198+ if ( typeof tmpComplexity === 'number' ) {
199+ nodeComplexity = tmpComplexity ;
200+ return true ;
201+ }
202+
203+ return false ;
204+ } ) ;
205+ if ( ! validScore ) {
206+ throw new Error (
207+ `No complexity could be calculated for field ${ typeDef . astNode } .${ field . name } . ` +
208+ 'Make sure you always have at least one estimator configured that returns a value.'
209+ ) ;
210+ }
211+ // nodeComplexity = this.getDefaultComplexity(args, childComplexity);
162212 }
163213 break ;
164214 }
@@ -205,8 +255,4 @@ export default class QueryComplexity {
205255 this . complexity
206256 ) ) ;
207257 }
208-
209- getDefaultComplexity ( args : Object , childScore : number ) : number {
210- return 1 + childScore ;
211- }
212258}
0 commit comments