1
1
import { parse } from 'graphql/language/parser' ;
2
- import Logger from './logger' ;
3
2
import Model from './model' ;
4
3
import { print } from 'graphql/language/printer' ;
5
- import { Arguments , Data , Field } from './interfaces' ;
6
- import { downcaseFirstLetter , upcaseFirstLetter } from './utils' ;
4
+ import { Arguments , Field } from './interfaces' ;
5
+ import { upcaseFirstLetter } from './utils' ;
7
6
import gql from 'graphql-tag' ;
8
7
import Context from './context' ;
9
8
10
- const inflection = require ( 'inflection' ) ;
11
-
12
9
/**
13
10
* Contains all logic to build GraphQL queries and transform variables between the format Vuex-ORM requires and the
14
11
* format of the GraphQL API.
15
12
*/
16
13
export default class QueryBuilder {
17
- /**
18
- * Context
19
- */
20
- private readonly context : Context ;
21
-
22
- /**
23
- * Constructor.
24
- * @param {Logger } logger
25
- * @param {(name: (Model | string)) => Model } getModel
26
- */
27
- public constructor ( context : Context ) {
28
- this . context = context ;
29
- }
30
-
31
14
/**
32
15
* Takes a string with a graphql query and formats it. Useful for debug output and the tests.
33
16
* @param {string } query
@@ -57,7 +40,9 @@ export default class QueryBuilder {
57
40
ignoreModels : Array < Model > = [ ] ,
58
41
name ?: string ,
59
42
allowIdFields : boolean = false ) : string {
60
- model = this . getModel ( model ) ;
43
+
44
+ const context = Context . getInstance ( ) ;
45
+ model = context . getModel ( model ) ;
61
46
ignoreModels . push ( model ) ;
62
47
63
48
let params : string = this . buildArguments ( model , args , false , multiple , allowIdFields ) ;
@@ -95,8 +80,10 @@ export default class QueryBuilder {
95
80
* @returns {any }
96
81
*/
97
82
public buildQuery ( type : string , model : Model | string , name ?: string , args ?: Arguments , multiple ?: boolean ) {
83
+ const context = Context . getInstance ( ) ;
84
+
98
85
// model
99
- model = this . getModel ( model ) ;
86
+ model = context . getModel ( model ) ;
100
87
if ( ! model ) throw new Error ( 'No model provided to build the query!' ) ;
101
88
102
89
// args
@@ -124,104 +111,6 @@ export default class QueryBuilder {
124
111
return gql ( query ) ;
125
112
}
126
113
127
- /**
128
- * Transforms outgoing data. Use for variables param.
129
- *
130
- * Omits relations and some fields.
131
- *
132
- * @param model
133
- * @param {Data } data
134
- * @returns {Data }
135
- */
136
- public transformOutgoingData ( model : Model , data : Data ) : Data {
137
- const relations : Map < string , Field > = model . getRelations ( ) ;
138
- const returnValue : Data = { } ;
139
-
140
- Object . keys ( data ) . forEach ( ( key ) => {
141
- const value = data [ key ] ;
142
-
143
- // Ignore hasMany/One connections, empty fields and internal fields ($)
144
- if ( ( ! relations . has ( key ) || relations . get ( key ) instanceof this . context . components . BelongsTo ) &&
145
- ! key . startsWith ( '$' ) && value !== null ) {
146
-
147
- if ( value instanceof Array ) {
148
- // Iterate over all fields and transform them if value is an array
149
- const arrayModel = this . getModel ( inflection . singularize ( key ) ) ;
150
- returnValue [ key ] = value . map ( ( v ) => this . transformOutgoingData ( arrayModel || model , v ) ) ;
151
- } else if ( typeof value === 'object' && this . context . getModel ( inflection . singularize ( key ) , true ) ) {
152
- // Value is a record, transform that too
153
- const relatedModel = this . getModel ( inflection . singularize ( key ) ) ;
154
- returnValue [ key ] = this . transformOutgoingData ( relatedModel || model , value ) ;
155
- } else {
156
- // In any other case just let the value be what ever it is
157
- returnValue [ key ] = value ;
158
- }
159
- }
160
- } ) ;
161
-
162
- return returnValue ;
163
- }
164
-
165
- /**
166
- * Transforms a set of incoming data to the format vuex-orm requires.
167
- *
168
- * @param {Data | Array<Data> } data
169
- * @param model
170
- * @param mutation required to transform something like `disableUserAddress` to the actual model name.
171
- * @param {boolean } recursiveCall
172
- * @returns {Data }
173
- */
174
- public transformIncomingData ( data : Data | Array < Data > , model : Model , mutation : boolean = false , recursiveCall : boolean = false ) : Data {
175
- let result : Data = { } ;
176
-
177
- if ( ! recursiveCall ) {
178
- this . context . logger . group ( 'Transforming incoming data' ) ;
179
- this . context . logger . log ( 'Raw data:' , data ) ;
180
- }
181
-
182
- if ( data instanceof Array ) {
183
- result = data . map ( d => this . transformIncomingData ( d , model , mutation , true ) ) ;
184
- } else {
185
- Object . keys ( data ) . forEach ( ( key ) => {
186
- if ( data [ key ] !== undefined && data [ key ] !== null ) {
187
- if ( data [ key ] instanceof Object ) {
188
- const localModel : Model = this . context . getModel ( key , true ) || model ;
189
-
190
- if ( data [ key ] . nodes ) {
191
- result [ inflection . pluralize ( key ) ] = this . transformIncomingData ( data [ key ] . nodes ,
192
- localModel , mutation , true ) ;
193
- } else {
194
- let newKey = key ;
195
-
196
- if ( mutation && ! recursiveCall ) {
197
- newKey = data [ key ] . nodes ? localModel . pluralName : localModel . singularName ;
198
- newKey = downcaseFirstLetter ( newKey ) ;
199
- }
200
-
201
- result [ newKey ] = this . transformIncomingData ( data [ key ] , localModel , mutation , true ) ;
202
- }
203
- } else if ( model . fieldIsNumber ( model . fields . get ( key ) ) ) {
204
- result [ key ] = parseFloat ( data [ key ] ) ;
205
- } else if ( key . endsWith ( 'Type' ) && model . isTypeFieldOfPolymorphRelation ( key ) ) {
206
- result [ key ] = inflection . pluralize ( downcaseFirstLetter ( data [ key ] ) ) ;
207
- } else {
208
- result [ key ] = data [ key ] ;
209
- }
210
- }
211
- } ) ;
212
- }
213
-
214
- if ( ! recursiveCall ) {
215
- this . context . logger . log ( 'Transformed data:' , result ) ;
216
- this . context . logger . groupEnd ( ) ;
217
- } else {
218
- result [ '$isPersisted' ] = true ;
219
- }
220
-
221
- // MAke sure this is really a plain JS object. We had some issues in testing here.
222
- return JSON . parse ( JSON . stringify ( result ) ) ;
223
- }
224
-
225
114
/**
226
115
* Generates the arguments string for a graphql query based on a given map.
227
116
*
@@ -306,12 +195,13 @@ export default class QueryBuilder {
306
195
*/
307
196
private determineAttributeType ( model : Model , key : string , value : any ) : string {
308
197
const field : undefined | Field = model . fields . get ( key ) ;
198
+ const context = Context . getInstance ( ) ;
309
199
310
- if ( field && field instanceof this . context . components . String ) {
200
+ if ( field && field instanceof context . components . String ) {
311
201
return 'String' ;
312
- } else if ( field && field instanceof this . context . components . Number ) {
202
+ } else if ( field && field instanceof context . components . Number ) {
313
203
return 'Int' ;
314
- } else if ( field && field instanceof this . context . components . Boolean ) {
204
+ } else if ( field && field instanceof context . components . Boolean ) {
315
205
return 'Boolean' ;
316
206
} else {
317
207
if ( typeof value === 'number' ) return 'Int' ;
@@ -331,26 +221,26 @@ export default class QueryBuilder {
331
221
private buildRelationsQuery ( model : ( null | Model ) , ignoreModels : Array < Model > = [ ] ) : string {
332
222
if ( model === null ) return '' ;
333
223
224
+ const context = Context . getInstance ( ) ;
334
225
const relationQueries : Array < string > = [ ] ;
335
226
336
227
model . getRelations ( ) . forEach ( ( field : Field , name : string ) => {
337
228
let relatedModel : Model ;
338
229
339
230
if ( field . related ) {
340
- relatedModel = this . getModel ( field . related . entity ) ;
231
+ relatedModel = context . getModel ( field . related . entity ) ;
341
232
} else if ( field . parent ) {
342
- relatedModel = this . getModel ( field . parent . entity ) ;
233
+ relatedModel = context . getModel ( field . parent . entity ) ;
343
234
} else {
344
- relatedModel = this . getModel ( name ) ;
345
- this . context . logger . log ( 'WARNING: field has neither parent nor related property. Fallback to attribute name' ,
346
- field ) ;
235
+ relatedModel = context . getModel ( name ) ;
236
+ context . logger . log ( 'WARNING: field has neither parent nor related property. Fallback to attribute name' , field ) ;
347
237
}
348
238
349
239
if ( this . shouldEagerLoadRelation ( model , field , relatedModel ) &&
350
240
! this . shouldModelBeIgnored ( relatedModel , ignoreModels ) ) {
351
241
352
- const multiple : boolean = ! ( field instanceof this . context . components . BelongsTo ||
353
- field instanceof this . context . components . HasOne ) ;
242
+ const multiple : boolean = ! ( field instanceof context . components . BelongsTo ||
243
+ field instanceof context . components . HasOne ) ;
354
244
355
245
relationQueries . push ( this . buildField ( relatedModel , multiple , undefined , ignoreModels , name ) ) ;
356
246
}
@@ -369,7 +259,9 @@ export default class QueryBuilder {
369
259
* @returns {boolean }
370
260
*/
371
261
private shouldEagerLoadRelation ( model : Model , field : Field , relatedModel : Model ) : boolean {
372
- if ( field instanceof this . context . components . HasOne || field instanceof this . context . components . BelongsTo ) {
262
+ const context = Context . getInstance ( ) ;
263
+
264
+ if ( field instanceof context . components . HasOne || field instanceof context . components . BelongsTo ) {
373
265
return true ;
374
266
}
375
267
@@ -380,13 +272,4 @@ export default class QueryBuilder {
380
272
private shouldModelBeIgnored ( model : Model , ignoreModels : Array < Model > ) : boolean {
381
273
return ignoreModels . find ( ( m ) => m . singularName === model . singularName ) !== undefined ;
382
274
}
383
-
384
- /**
385
- * Helper method to get the model by name
386
- * @param {Model|string } name
387
- * @returns {Model }
388
- */
389
- private getModel ( name : Model | string ) : Model {
390
- return this . context . getModel ( name ) ;
391
- }
392
275
}
0 commit comments