1- import { print } from 'graphql/language/printer' ;
2- import { parse } from 'graphql/language/parser' ;
3- import { Arguments , Data , Field } from './interfaces' ;
4- import Model from './model' ;
5- import gql from 'graphql-tag' ;
6- import Logger from './logger' ;
7- import { downcaseFirstLetter , upcaseFirstLetter } from './utils' ;
1+ import { parse } from "graphql/language/parser" ;
2+ import Logger from "./logger" ;
3+ import Model from "./model" ;
4+ import { print } from "graphql/language/printer" ;
5+ import { Arguments , Data , Field } from "./interfaces" ;
6+ import { downcaseFirstLetter , upcaseFirstLetter } from "./utils" ;
7+ import gql from "graphql-tag" ;
8+
89const inflection = require ( 'inflection' ) ;
910
10- /**
11- * This class takes care of everything GraphQL query related, especially the generation of queries out of models
12- */
1311export default class QueryBuilder {
1412 private readonly logger : Logger ;
1513 private readonly getModel : ( name : Model | string ) => Model ;
@@ -33,6 +31,7 @@ export default class QueryBuilder {
3331 return print ( parse ( query ) ) ;
3432 }
3533
34+
3635 /**
3736 * Generates the arguments string for a graphql query based on a given map.
3837 *
@@ -44,25 +43,17 @@ export default class QueryBuilder {
4443 * 2) Signatures with object types (signature = true, args = { user: { __type: 'User' }})
4544 * mutation createUser($user: UserInput!)
4645 *
47- * 3) Fields with values (signature = false, valuesAsVariables = false)
48- * query user(id: 15)
49- *
50- * 4) Fields with variables (signature = false, valuesAsVariables = true)
46+ * 3) Fields with variables (signature = false, valuesAsVariables = true)
5147 * query user(id: $id)
5248 *
53- * 5) Fields with object value (signature = false, valuesAsVariables = false, args = { user: { __type: 'User' }})
54- * mutation createUser(user: {...})
55- *
5649 * @param {Arguments | undefined } args
5750 * @param {boolean } signature When true, then this method generates a query signature instead of key/value pairs
58- * @param {boolean } valuesAsVariables When true and abstract = false, then this method generates filter arguments with
59- * variables instead of values
51+ * @param {boolean } allowIdFields If true, ID fields will be included in the arguments list
6052 * @returns {String }
6153 */
62- public buildArguments ( args : Arguments | undefined ,
63- signature : boolean = false ,
64- valuesAsVariables : boolean = false ,
65- allowIdFields : boolean = false ) : string {
54+ private buildArguments ( args ?: Arguments , signature : boolean = false , allowIdFields : boolean = true ) : string {
55+ if ( args === null ) return '' ;
56+
6657 let returnValue : string = '' ;
6758 let first : boolean = true ;
6859
@@ -85,17 +76,9 @@ export default class QueryBuilder {
8576 // Case 1 (String!)
8677 typeOrValue = typeof value === 'number' ? 'Number!' : 'String!' ;
8778 }
88- } else if ( valuesAsVariables ) {
89- // Case 6 (user: $user)
90- typeOrValue = `$${ key } ` ;
9179 } else {
92- if ( typeof value === 'object' && value . __type ) {
93- // Case 3 ({name: 'Helga Hufflepuff"})
94- typeOrValue = JSON . stringify ( value ) ;
95- } else {
96- // Case 3 ("someValue")
97- typeOrValue = typeof value === 'number' ? value : `"${ value } "` ;
98- }
80+ // Case 3 (user: $user)
81+ typeOrValue = `$${ key } ` ;
9982 }
10083
10184 returnValue = `${ returnValue } ${ first ? '' : ', ' } ${ ( signature ? '$' : '' ) + key } : ${ typeOrValue } ` ;
@@ -109,6 +92,96 @@ export default class QueryBuilder {
10992 return returnValue ;
11093 }
11194
95+
96+
97+ /**
98+ * Builds a field for the GraphQL query and a specific model
99+ *
100+ * @param {Model|string } model
101+ * @param {boolean } multiple
102+ * @param {Arguments } args
103+ * @param {Model } rootModel
104+ * @param {string } name
105+ * @param allowIdFields
106+ * @returns {string }
107+ */
108+ public buildField ( model : Model | string ,
109+ multiple : boolean = true ,
110+ args ?: Arguments ,
111+ rootModel ?: Model ,
112+ name ?: string ,
113+ allowIdFields : boolean = false ) : string {
114+ model = this . getModel ( model ) ;
115+
116+ let params : string = this . buildArguments ( args , false , allowIdFields ) ;
117+
118+ const fields = `
119+ ${ model . getQueryFields ( ) . join ( ' ' ) }
120+ ${ this . buildRelationsQuery ( model , rootModel ) }
121+ ` ;
122+
123+ if ( multiple ) {
124+ return `
125+ ${ name ? name : model . pluralName } ${ params } {
126+ nodes {
127+ ${ fields }
128+ }
129+ }
130+ ` ;
131+ } else {
132+ return `
133+ ${ name ? name : model . singularName } ${ params } {
134+ ${ fields }
135+ }
136+ ` ;
137+ }
138+ }
139+
140+
141+ /**
142+ *
143+ * @param {Model } model
144+ * @param {Model } rootModel
145+ * @returns {Array<String> }
146+ */
147+ private buildRelationsQuery ( model : ( null | Model ) , rootModel ?: Model ) {
148+ if ( model === null ) return '' ;
149+
150+ const relationQueries : Array < string > = [ ] ;
151+
152+ model . getRelations ( ) . forEach ( ( field : Field , name : string ) => {
153+ if ( ! rootModel || ( name !== rootModel . singularName && name !== rootModel . pluralName ) ) {
154+ const multiple : boolean = field . constructor . name !== 'BelongsTo' ;
155+ relationQueries . push ( this . buildField ( name , multiple , undefined , rootModel || model ) ) ;
156+ }
157+ } ) ;
158+
159+ return relationQueries ;
160+ }
161+
162+
163+
164+ public buildQuery ( type : string , name ?: string , args ?: Arguments , model ?: ( Model | null | string ) , fields ?: string , addModelToArgs :boolean = false , multiple ?: boolean ) {
165+ model = model ? this . getModel ( model ) : null ;
166+
167+ if ( ! args ) args = { } ;
168+ if ( addModelToArgs && model ) args [ model . singularName ] = { __type : upcaseFirstLetter ( model . singularName ) } ;
169+
170+ multiple = multiple === undefined ? ! args [ 'id' ] : multiple ;
171+
172+ if ( ! name && model ) name = ( multiple ? model . pluralName : model . singularName ) ;
173+ if ( ! name ) throw new Error ( "Can't determine name for the query! Please provide either name or model" ) ;
174+
175+
176+ const query :string =
177+ `${ type } ${ upcaseFirstLetter ( name ) } ${ this . buildArguments ( args , true ) } {\n` +
178+ ` ${ model ? this . buildField ( model , multiple , args , model , name , true ) : fields } \n` +
179+ `}` ;
180+
181+ return gql ( query ) ;
182+ }
183+
184+
112185 /**
113186 * Transforms outgoing data. Use for variables param.
114187 *
@@ -183,114 +256,4 @@ export default class QueryBuilder {
183256
184257 return result ;
185258 }
186-
187- /**
188- *
189- * @param {Model } model
190- * @param {Model } rootModel
191- * @returns {Array<String> }
192- */
193- public buildRelationsQuery ( model : Model , rootModel ?: Model ) {
194- const relationQueries : Array < string > = [ ] ;
195-
196- model . getRelations ( ) . forEach ( ( field : Field , name : string ) => {
197- if ( ! rootModel || ( name !== rootModel . singularName && name !== rootModel . pluralName ) ) {
198- const multiple : boolean = field . constructor . name !== 'BelongsTo' ;
199- relationQueries . push ( this . buildField ( name , multiple , undefined , false , rootModel || model ) ) ;
200- }
201- } ) ;
202-
203- return relationQueries ;
204- }
205-
206- /**
207- * Builds a field for the GraphQL query and a specific model
208- * @param {Model|string } model
209- * @param {boolean } multiple
210- * @param {Arguments } args
211- * @param {boolean } withVars
212- * @param {Model } rootModel
213- * @param {string } name
214- * @returns {string }
215- */
216- public buildField ( model : Model | string ,
217- multiple : boolean = true ,
218- args ?: Arguments ,
219- withVars : boolean = false ,
220- rootModel ?: Model ,
221- name ?: string ,
222- allowIdFields : boolean = false ) : string {
223- model = this . getModel ( model ) ;
224-
225- let params : string = this . buildArguments ( args , false , withVars , allowIdFields ) ;
226-
227- const fields = `
228- ${ model . getQueryFields ( ) . join ( ' ' ) }
229- ${ this . buildRelationsQuery ( model , rootModel ) }
230- ` ;
231-
232- if ( multiple ) {
233- return `
234- ${ name ? name : model . pluralName } ${ params } {
235- nodes {
236- ${ fields }
237- }
238- }
239- ` ;
240- } else {
241- return `
242- ${ name ? name : model . singularName } ${ params } {
243- ${ fields }
244- }
245- ` ;
246- }
247- }
248-
249- /**
250- * Create a GraphQL query for the given model and arguments.
251- *
252- * @param {string } modelName
253- * @param {Arguments } args
254- * @returns {any }
255- */
256- public buildQuery ( modelName : string , args ?: Arguments ) : any {
257- // Ignore empty args
258- if ( args && Object . keys ( args ) . length === 0 ) args = undefined ;
259-
260- const multiple = ! ( args && args . get ( 'id' ) ) ;
261- const query = `{ ${ this . buildField ( modelName , multiple , args ) } }` ;
262- return gql ( query ) ;
263- }
264-
265- /**
266- * Generates a mutation query for a model.
267- *
268- * @param {Model } model
269- * @param {string }prefix
270- * @returns {any }
271- *
272- * TODO: Refactor to avoid prefix param
273- */
274- public buildMutation ( model : Model , id ?: number , prefix : string = 'create' ) {
275- const name : string = `${ prefix } ${ upcaseFirstLetter ( model . singularName ) } ` ;
276- let args : Data = { [ model . singularName ] : { __type : upcaseFirstLetter ( model . singularName ) } } ;
277-
278- if ( prefix === 'delete' ) {
279- if ( ! id ) throw new Error ( 'No ID given.' ) ;
280- args = { id } ;
281- } else if ( prefix === 'update' ) {
282- args [ 'id' ] = id ;
283- }
284-
285- const signature : string = this . buildArguments ( args , true , false , true ) ;
286- const field = this . buildField ( model , false , args , true , model , name , true ) ;
287-
288- const query = `
289- mutation ${ name } ${ signature } {
290- ${ field }
291- }
292- ` ;
293-
294- return gql ( query ) ;
295- }
296259}
0 commit comments