Skip to content

Commit b7e9c42

Browse files
committed
Refactorings & Tests
1 parent e96cdf0 commit b7e9c42

File tree

6 files changed

+322
-219
lines changed

6 files changed

+322
-219
lines changed

src/index.ts

Lines changed: 11 additions & 219 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import gql from 'graphql-tag';
66
import { Data, ActionParams, Field, Arguments, ORMModel } from './interfaces';
77
import Logger from './logger';
88
import { FetchResult } from "apollo-link";
9+
import QueryBuilder from './queryBuilder';
10+
import { capitalizeFirstLetter } from './utils';
911

1012
const inflection = require('inflection');
1113

1214
/**
1315
* Plugin class
14-
* TODO: Refactor to smaller classes
1516
*/
1617
export default class VuexORMApollo {
1718
private readonly httpLink: HttpLink;
@@ -22,6 +23,7 @@ export default class VuexORMApollo {
2223
private readonly models: Map<string, Model> = new Map();
2324
private readonly debugMode: boolean = false;
2425
private readonly logger: Logger;
26+
private readonly queryBuilder: QueryBuilder;
2527

2628
/**
2729
* Constructor
@@ -53,6 +55,8 @@ export default class VuexORMApollo {
5355
cache: new InMemoryCache(),
5456
connectToDevTools: true
5557
});
58+
59+
this.queryBuilder = new QueryBuilder(this.logger, this.getModel.bind(this));
5660
}
5761

5862
/**
@@ -102,7 +106,7 @@ export default class VuexORMApollo {
102106
if (filter && Object.keys(filter).length === 0) filter = undefined;
103107

104108
// Send the request to the GraphQL API
105-
const query = this.buildQuery(state.$name, filter);
109+
const query = this.queryBuilder.buildQuery(state.$name, filter);
106110
const data = await this.apolloRequest(query);
107111

108112
// Insert incoming data into the store
@@ -119,17 +123,17 @@ export default class VuexORMApollo {
119123
*/
120124
private async persist ({ state, dispatch }: ActionParams, { id }: ActionParams): Promise<FetchResult> {
121125
const model = this.getModel(state.$name);
122-
const name = `create${VuexORMApollo.capitalizeFirstLetter(model.singularName)}`;
126+
const name = `create${capitalizeFirstLetter(model.singularName)}`;
123127

124128

125129
const data = model.baseModel.getters('find', { id })();
126130

127131
// Send the request to the GraphQL API
128-
const signature = VuexORMApollo.buildArguments({contract: {__type: 'Contract'}}, true);
132+
const signature = this.queryBuilder.buildArguments({contract: {__type: 'Contract'}}, true);
129133

130134
const query = `
131135
mutation ${name}${signature} {
132-
${this.buildField(model, false, {contract: {__type: 'Contract'}}, true, undefined, name)}
136+
${this.queryBuilder.buildField(model, false, {contract: {__type: 'Contract'}}, true, undefined, name)}
133137
}
134138
`;
135139

@@ -141,7 +145,7 @@ export default class VuexORMApollo {
141145
const newData = await this.apolloClient.mutate({
142146
"mutation": gql(query),
143147
"variables": {
144-
[model.singularName]: this.transformOutgoingData(data)
148+
[model.singularName]: this.queryBuilder.transformOutgoingData(data)
145149
}
146150
});
147151

@@ -157,135 +161,6 @@ export default class VuexORMApollo {
157161

158162
}
159163

160-
161-
private transformOutgoingData(data: Data): Data {
162-
const returnValue: Data = {};
163-
164-
Object.keys(data).forEach((key) => {
165-
const value = data[key];
166-
167-
// Ignore IDs and connections
168-
if (!(value instanceof Array || key === 'id')) {
169-
returnValue[key] = value;
170-
}
171-
});
172-
173-
return returnValue;
174-
}
175-
176-
177-
/**
178-
* Transforms a set of incoming data to the format vuex-orm requires.
179-
*
180-
* @param {Data | Array<Data>} data
181-
* @param {boolean} recursiveCall
182-
* @returns {Data}
183-
*/
184-
private transformIncomingData (data: Data | Array<Data>, recursiveCall: boolean = false): Data {
185-
let result: Data = {};
186-
187-
if (!recursiveCall) {
188-
this.logger.group('Transforming incoming data');
189-
this.logger.log('Raw data:', data);
190-
}
191-
192-
if (data instanceof Array) {
193-
result = data.map(d => this.transformIncomingData(d, true));
194-
} else {
195-
Object.keys(data).forEach((key) => {
196-
if (data[key]) {
197-
if (data[key] instanceof Object) {
198-
if (data[key].nodes) {
199-
result[inflection.pluralize(key)] = this.transformIncomingData(data[key].nodes, true);
200-
} else {
201-
result[inflection.singularize(key)] = this.transformIncomingData(data[key], true);
202-
}
203-
} else if (key === 'id') {
204-
result[key] = parseInt(data[key], 0);
205-
} else {
206-
result[key] = data[key];
207-
}
208-
}
209-
});
210-
}
211-
212-
if (!recursiveCall) {
213-
this.logger.log('Transformed data:', result);
214-
this.logger.groupEnd();
215-
}
216-
217-
return result;
218-
}
219-
220-
/**
221-
*
222-
* @param {Model} model
223-
* @param {Model} rootModel
224-
* @returns {Array<String>}
225-
*/
226-
private buildRelationsQuery (model: Model, rootModel?: Model) {
227-
const relationQueries: Array<string> = [];
228-
229-
model.getRelations().forEach((field: Field, name: string) => {
230-
if (!rootModel || name !== rootModel.singularName && name !== rootModel.pluralName) {
231-
const multiple: boolean = field.constructor.name !== 'BelongsTo';
232-
relationQueries.push(this.buildField(name, multiple, undefined, false, rootModel || model));
233-
}
234-
});
235-
236-
return relationQueries;
237-
}
238-
239-
/**
240-
* Builds a field for the GraphQL query and a specific model
241-
* @param {Model|string} model
242-
* @param {boolean} multiple
243-
* @param {Arguments} args
244-
* @param {boolean} withVars
245-
* @param {Model} rootModel
246-
* @param {string} name
247-
* @returns {string}
248-
*/
249-
private buildField (model: Model|string, multiple: boolean = true, args?: Arguments, withVars: boolean = false, rootModel?: Model, name?: string): string {
250-
model = this.getModel(model);
251-
252-
let params: string = VuexORMApollo.buildArguments(args, false, withVars);
253-
254-
const fields = `
255-
${model.getQueryFields().join(' ')}
256-
${this.buildRelationsQuery(model, rootModel)}
257-
`;
258-
259-
if (multiple) {
260-
return `
261-
${name ? name : model.pluralName}${params} {
262-
nodes {
263-
${fields}
264-
}
265-
}
266-
`;
267-
} else {
268-
return `
269-
${name ? name : model.singularName}${params} {
270-
${fields}
271-
}
272-
`;
273-
}
274-
}
275-
276-
/**
277-
* Create a GraphQL query for the given model and arguments.
278-
*
279-
* @param {string} modelName
280-
* @param {Arguments} args
281-
* @returns {any}
282-
*/
283-
private buildQuery (modelName: string, args?: Arguments): any {
284-
const multiple = !(args && args.get('id'));
285-
const query = `{ ${this.buildField(modelName, multiple, args)} }`;
286-
return gql(query);
287-
}
288-
289164
/**
290165
* Sends a query to the GraphQL API via apollo
291166
* @param query
@@ -295,7 +170,7 @@ export default class VuexORMApollo {
295170
const response = await (this.apolloClient).query({ query });
296171

297172
// Transform incoming data into something useful
298-
return this.transformIncomingData(response.data);
173+
return this.queryBuilder.transformIncomingData(response.data);
299174
}
300175

301176
/**
@@ -324,87 +199,4 @@ export default class VuexORMApollo {
324199

325200
return model;
326201
}
327-
328-
/**
329-
* Capitalizes the first letter of the given string.
330-
*
331-
* @param {string} string
332-
* @returns {string}
333-
*/
334-
private static capitalizeFirstLetter(string: string) {
335-
return string.charAt(0).toUpperCase() + string.slice(1);
336-
}
337-
338-
339-
/**
340-
* Generates the arguments string for a graphql query based on a given map.
341-
*
342-
* There are three types of arguments:
343-
*
344-
* 1) Signatures with attributes (signature = true)
345-
* mutation createUser($name: String!)
346-
*
347-
* 2) Signatures with object (signature = true, args = { user: { __type: 'User' }})
348-
* mutation createUser($user: User!)
349-
*
350-
* 3) Field with values (signature = false, valuesAsVariables = false)
351-
* user(id: 15)
352-
*
353-
* 4) Field with variables (signature = false, valuesAsVariables = true)
354-
* user(id: $id)
355-
*
356-
* 5) Field with object value (signature = false, valuesAsVariables = false, args = { user: { __type: 'User' }})
357-
* createUser(user: {...})
358-
*
359-
* @param {Arguments | undefined} args
360-
* @param {boolean} signature When true, then this method generates a query signature instead of key/value pairs
361-
* @param {boolean} valuesAsVariables When true and abstract = false, then this method generates filter arguments with
362-
* variables instead of values
363-
* TODO: Query with variables too?
364-
* @returns {String}
365-
*/
366-
private static buildArguments(args: Arguments | undefined, signature: boolean = false,
367-
valuesAsVariables: boolean = false): string {
368-
let returnValue: string = '';
369-
let any: boolean = false;
370-
371-
if (args) {
372-
Object.keys(args).forEach((key: string) => {
373-
let value: any = args[key];
374-
375-
// Ignore ids and connections
376-
if (!(value instanceof Array || key === 'id')) {
377-
any = true;
378-
let typeOrValue: any = '';
379-
380-
if (signature) {
381-
if (typeof value === 'object' && value.__type) {
382-
// Case 2 (User!)
383-
typeOrValue = value.__type + 'Input!';
384-
} else {
385-
// Case 1 (String!)
386-
typeOrValue = typeof value === 'number' ? 'Number!' : 'String!';
387-
}
388-
} else if (valuesAsVariables) {
389-
// Case 6 (user: $user)
390-
typeOrValue = `$${key}`;
391-
} else {
392-
if (typeof value === 'object' && value.__type) {
393-
// Case 3 ({name: 'Helga Hufflepuff"})
394-
typeOrValue = value;
395-
} else {
396-
// Case 3 ("someValue")
397-
typeOrValue = typeof value === 'number' ? value : `"${value}"`;
398-
}
399-
}
400-
401-
returnValue = `${returnValue} ${(signature ? '$' : '') + key}: ${typeOrValue}`;
402-
}
403-
});
404-
405-
if (any) returnValue = `(${returnValue})`;
406-
}
407-
408-
return returnValue;
409-
}
410202
}

src/interfaces.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import Model from "./model";
2+
13
export interface ActionParams {
24
commit: any;
35
dispatch: any;

0 commit comments

Comments
 (0)