11import {
2+ ArgumentNode ,
23 GraphQLArgument ,
3- GraphQLEnumType ,
44 GraphQLFieldMap ,
5- GraphQLInterfaceType ,
6- GraphQLList ,
75 GraphQLNamedType ,
8- GraphQLNonNull ,
96 GraphQLObjectType ,
107 GraphQLOutputType ,
11- GraphQLScalarType ,
12- GraphQLUnionType ,
8+ IntValueNode ,
139 isCompositeType ,
10+ isEnumType ,
11+ isInterfaceType ,
12+ isListType ,
13+ isNonNullType ,
14+ isObjectType ,
15+ isScalarType ,
16+ isUnionType ,
17+ ValueNode ,
1418} from 'graphql' ;
1519import { Maybe } from 'graphql/jsutils/Maybe' ;
20+ import { ObjMap } from 'graphql/jsutils/ObjMap' ;
1621import { GraphQLSchema } from 'graphql/type/schema' ;
1722
1823const KEYWORDS = [ 'first' , 'last' , 'limit' ] ;
@@ -24,12 +29,17 @@ const KEYWORDS = ['first', 'last', 'limit'];
2429 * scalar: 0
2530 * connection: 2
2631 */
32+
33+ // These variables exist to provide a default value for typescript when accessing a weight
34+ // since all props are optioal in TypeWeightConfig
2735const DEFAULT_MUTATION_WEIGHT = 10 ;
2836const DEFAULT_OBJECT_WEIGHT = 1 ;
2937const DEFAULT_SCALAR_WEIGHT = 0 ;
3038const DEFAULT_CONNECTION_WEIGHT = 2 ;
3139const DEFAULT_QUERY_WEIGHT = 1 ;
3240
41+ // FIXME: What about Union, Enum and Interface defaults
42+
3343export const defaultTypeWeightsConfig : TypeWeightConfig = {
3444 mutation : DEFAULT_MUTATION_WEIGHT ,
3545 object : DEFAULT_OBJECT_WEIGHT ,
@@ -43,8 +53,8 @@ export const defaultTypeWeightsConfig: TypeWeightConfig = {
4353 * back on shopifys settings. We can change this later.
4454 *
4555 * This function should
46- * - TODO: iterate through the schema object and create the typeWeightObject as described in the tests
47- * - TODO: validate that the typeWeightsConfig parameter has no negative values (throw an error if it does)
56+ * - iterate through the schema object and create the typeWeightObject as described in the tests
57+ * - validate that the typeWeightsConfig parameter has no negative values (throw an error if it does)
4858 *
4959 * @param schema
5060 * @param typeWeightsConfig Defaults to {mutation: 10, object: 1, field: 0, connection: 2}
@@ -53,14 +63,6 @@ function buildTypeWeightsFromSchema(
5363 schema : GraphQLSchema ,
5464 typeWeightsConfig : TypeWeightConfig = defaultTypeWeightsConfig
5565) : TypeWeightObject {
56- // Iterate each key in the schema object
57- // this includes scalars, types, interfaces, unions, enums etc.
58- // check the type of each add set the appropriate weight.
59- // iterate through that types fields and set the appropriate weight
60- // this is kind of only relevant for things like Query or Mutation
61- // that have functions(?) as fields for which we should set the weight as a function
62- // that take any required params.
63-
6466 if ( ! schema ) throw new Error ( 'Must provide schema' ) ;
6567
6668 // Merge the provided type weights with the default to account for missing values
@@ -78,50 +80,44 @@ function buildTypeWeightsFromSchema(
7880
7981 const result : TypeWeightObject = { } ;
8082
81- // Iterate through __typeMap and set weights of all object types?
82-
83- const typeMap = schema . getTypeMap ( ) ;
83+ const typeMap : ObjMap < GraphQLNamedType > = schema . getTypeMap ( ) ;
8484
85+ // Handle Object, Interface, Enum and Union types
8586 Object . keys ( typeMap ) . forEach ( ( type ) => {
8687 const currentType : GraphQLNamedType = typeMap [ type ] ;
87- // Limit to object types for now
88- // Get all types that aren't Query or Mutation and don't start with __
88+ // Get all types that aren't Query or Mutation or a built in type that starts with '__'
8989 if (
9090 currentType . name !== 'Query' &&
9191 currentType . name !== 'Mutation' &&
9292 ! currentType . name . startsWith ( '__' )
9393 ) {
94- if (
95- currentType instanceof GraphQLObjectType ||
96- currentType instanceof GraphQLInterfaceType
97- ) {
94+ if ( isObjectType ( currentType ) || isInterfaceType ( currentType ) ) {
9895 // Add the type to the result
9996 result [ type ] = {
10097 fields : { } ,
10198 weight : typeWeights . object || DEFAULT_OBJECT_WEIGHT ,
10299 } ;
103100
104101 const fields = currentType . getFields ( ) ;
102+
105103 Object . keys ( fields ) . forEach ( ( field : string ) => {
106104 const fieldType : GraphQLOutputType = fields [ field ] . type ;
107105 if (
108- fieldType instanceof GraphQLScalarType ||
109- ( fieldType instanceof GraphQLNonNull &&
110- fieldType . ofType instanceof GraphQLScalarType )
106+ isScalarType ( fieldType ) ||
107+ ( isNonNullType ( fieldType ) && isScalarType ( fieldType . ofType ) )
111108 ) {
112109 result [ type ] . fields [ field ] = typeWeights . scalar || DEFAULT_SCALAR_WEIGHT ;
113110 }
114- // FIXME: Do any other types need to be included?
115111 } ) ;
116- } else if ( currentType instanceof GraphQLEnumType ) {
112+ } else if ( isEnumType ( currentType ) ) {
117113 result [ currentType . name ] = {
118114 fields : { } ,
119- weight : 0 ,
115+ weight : typeWeights . scalar || DEFAULT_SCALAR_WEIGHT ,
120116 } ;
121- } else if ( currentType instanceof GraphQLUnionType ) {
117+ } else if ( isUnionType ( currentType ) ) {
122118 result [ currentType . name ] = {
123119 fields : { } ,
124- weight : 1 , // FIXME: Use the correct weight
120+ weight : typeWeights . object || DEFAULT_OBJECT_WEIGHT ,
125121 } ;
126122 }
127123 }
@@ -133,40 +129,79 @@ function buildTypeWeightsFromSchema(
133129 if ( queryType ) {
134130 result . Query = {
135131 weight : typeWeights . query || DEFAULT_QUERY_WEIGHT ,
136- fields : {
137- // This object gets populated with the query fields and associated weights.
138- } ,
132+ // fields gets populated with the query fields and associated weights.
133+ fields : { } ,
139134 } ;
135+
140136 const queryFields : GraphQLFieldMap < any , any > = queryType . getFields ( ) ;
137+
141138 Object . keys ( queryFields ) . forEach ( ( field ) => {
139+ // this is the type the query resolves to
142140 const resolveType : GraphQLOutputType = queryFields [ field ] . type ;
143141
142+ // check if any of our keywords 'first', 'last', 'limit' exist in the arg list
144143 queryFields [ field ] . args . forEach ( ( arg : GraphQLArgument ) => {
145- // check if any of our keywords 'first', 'last', 'limit' exist in the arglist
146- if ( KEYWORDS . includes ( arg . name ) && resolveType instanceof GraphQLList ) {
144+ // If query has an argument matching one of the limiting keywords and resolves to a list then the weight of the query
145+ // should be dependent on both the weight of the resolved type and the limiting argument.
146+ if ( KEYWORDS . includes ( arg . name ) && isListType ( resolveType ) ) {
147147 const defaultVal : number = < number > arg . defaultValue ;
148- // FIXME: How can we provide the complexity analysis algo with name of the argument to use?
148+
149+ // Get the type that comprises the list
149150 const listType = resolveType . ofType ;
151+
152+ // Composite Types are Objects, Interfaces and Unions.
150153 if ( isCompositeType ( listType ) ) {
151- result . Query . fields [ field ] = ( multiplier : number = defaultVal ) =>
152- multiplier * result [ listType . name ] . weight ;
154+ // Set the field weight to a function that accepts
155+ // TODO: Accept ArgumentNode[] and look for the arg we need.
156+ // TODO: Test this function
157+ result . Query . fields [ field ] = ( args : ArgumentNode [ ] ) : number => {
158+ // Function should receive object with arg, value as k, v pairs
159+ // function iterate on this object looking for a keyword then returns
160+ const limitArg : ArgumentNode | undefined = args . find (
161+ ( cur ) => cur . name . value === arg . name
162+ ) ;
163+
164+ // const isVariable = (node: any): node is VariableNode => {
165+ // if (node as VariableNode) return true;
166+ // return false;
167+ // };
168+
169+ const isIntNode = ( node : any ) : node is IntValueNode => {
170+ if ( node as IntValueNode ) return true ;
171+ return false ;
172+ } ;
173+
174+ if ( limitArg ) {
175+ const node : ValueNode = limitArg . value ;
176+
177+ // FIXME: Is there a better way to check for the type here?
178+ if ( isIntNode ( node ) ) {
179+ const multiplier = Number ( node . value || arg . defaultValue ) ;
180+
181+ return result [ listType . name ] . weight * multiplier ;
182+ }
183+ }
184+
185+ // FIXME: The list is unbounded. Return the object weight
186+ return result [ listType . name ] . weight ;
187+ } ;
188+ } else {
189+ // TODO: determine the type of the list and use the appropriate weight
190+ // TODO: This should multiply as well
191+ result . Query . fields [ field ] = typeWeights . scalar || DEFAULT_SCALAR_WEIGHT ;
153192 }
154193 }
155194 } ) ;
156195
157- // if the field is a scalars set weight accordingly
196+ // if the field is a scalar set weight accordingly
158197 // FIXME: Enums shouldn't be here???
159- if (
160- resolveType instanceof GraphQLScalarType ||
161- resolveType instanceof GraphQLEnumType
162- ) {
198+ if ( isScalarType ( resolveType ) || isEnumType ( resolveType ) ) {
163199 result . Query . fields [ field ] = typeWeights . scalar || DEFAULT_SCALAR_WEIGHT ;
164200 }
165201 } ) ;
166202 }
167203
168- // get the type of the field
169-
170204 return result ;
171205}
206+
172207export default buildTypeWeightsFromSchema ;
0 commit comments