Skip to content

Commit e5e8a2b

Browse files
committed
Massive refactoring and allow custom mutations
1 parent 9a1f33d commit e5e8a2b

File tree

3 files changed

+159
-177
lines changed

3 files changed

+159
-177
lines changed

src/queryBuilder.ts

Lines changed: 106 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
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+
89
const inflection = require('inflection');
910

10-
/**
11-
* This class takes care of everything GraphQL query related, especially the generation of queries out of models
12-
*/
1311
export 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

Comments
 (0)