Skip to content

Commit 83e7d24

Browse files
committed
Refactorings
1 parent 943081a commit 83e7d24

File tree

6 files changed

+521
-380
lines changed

6 files changed

+521
-380
lines changed

src/context.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,21 @@ import { downcaseFirstLetter } from './utils';
66
const inflection = require('inflection');
77

88
export default class Context {
9+
public static instance: Context;
10+
911
public readonly components: Components;
1012
public readonly options: any;
1113
public readonly database: any;
1214
public readonly models: Map<string, Model> = new Map();
1315
public readonly debugMode: boolean = false;
1416
public readonly logger: Logger;
1517

16-
public constructor (components: Components, options: Options) {
18+
/**
19+
* Private constructor, called by the setup method
20+
* @param {Components} components
21+
* @param {Options} options
22+
*/
23+
private constructor (components: Components, options: Options) {
1724
this.components = components;
1825
this.options = options;
1926

@@ -35,6 +42,15 @@ export default class Context {
3542
this.logger.groupEnd();
3643
}
3744

45+
public static getInstance () {
46+
return this.instance;
47+
}
48+
49+
public static setup (components: Components, options: Options) {
50+
this.instance = new Context(components, options);
51+
return this.instance;
52+
}
53+
3854
/**
3955
* Returns a model by name
4056
*

src/queryBuilder.ts

Lines changed: 22 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,16 @@
11
import { parse } from 'graphql/language/parser';
2-
import Logger from './logger';
32
import Model from './model';
43
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';
76
import gql from 'graphql-tag';
87
import Context from './context';
98

10-
const inflection = require('inflection');
11-
129
/**
1310
* Contains all logic to build GraphQL queries and transform variables between the format Vuex-ORM requires and the
1411
* format of the GraphQL API.
1512
*/
1613
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-
3114
/**
3215
* Takes a string with a graphql query and formats it. Useful for debug output and the tests.
3316
* @param {string} query
@@ -57,7 +40,9 @@ export default class QueryBuilder {
5740
ignoreModels: Array<Model> = [],
5841
name?: string,
5942
allowIdFields: boolean = false): string {
60-
model = this.getModel(model);
43+
44+
const context = Context.getInstance();
45+
model = context.getModel(model);
6146
ignoreModels.push(model);
6247

6348
let params: string = this.buildArguments(model, args, false, multiple, allowIdFields);
@@ -95,8 +80,10 @@ export default class QueryBuilder {
9580
* @returns {any}
9681
*/
9782
public buildQuery (type: string, model: Model | string, name?: string, args?: Arguments, multiple?: boolean) {
83+
const context = Context.getInstance();
84+
9885
// model
99-
model = this.getModel(model);
86+
model = context.getModel(model);
10087
if (!model) throw new Error('No model provided to build the query!');
10188

10289
// args
@@ -124,104 +111,6 @@ export default class QueryBuilder {
124111
return gql(query);
125112
}
126113

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-
225114
/**
226115
* Generates the arguments string for a graphql query based on a given map.
227116
*
@@ -306,12 +195,13 @@ export default class QueryBuilder {
306195
*/
307196
private determineAttributeType (model: Model, key: string, value: any): string {
308197
const field: undefined | Field = model.fields.get(key);
198+
const context = Context.getInstance();
309199

310-
if (field && field instanceof this.context.components.String) {
200+
if (field && field instanceof context.components.String) {
311201
return 'String';
312-
} else if (field && field instanceof this.context.components.Number) {
202+
} else if (field && field instanceof context.components.Number) {
313203
return 'Int';
314-
} else if (field && field instanceof this.context.components.Boolean) {
204+
} else if (field && field instanceof context.components.Boolean) {
315205
return 'Boolean';
316206
} else {
317207
if (typeof value === 'number') return 'Int';
@@ -331,26 +221,26 @@ export default class QueryBuilder {
331221
private buildRelationsQuery (model: (null | Model), ignoreModels: Array<Model> = []): string {
332222
if (model === null) return '';
333223

224+
const context = Context.getInstance();
334225
const relationQueries: Array<string> = [];
335226

336227
model.getRelations().forEach((field: Field, name: string) => {
337228
let relatedModel: Model;
338229

339230
if (field.related) {
340-
relatedModel = this.getModel(field.related.entity);
231+
relatedModel = context.getModel(field.related.entity);
341232
} else if (field.parent) {
342-
relatedModel = this.getModel(field.parent.entity);
233+
relatedModel = context.getModel(field.parent.entity);
343234
} 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);
347237
}
348238

349239
if (this.shouldEagerLoadRelation(model, field, relatedModel) &&
350240
!this.shouldModelBeIgnored(relatedModel, ignoreModels)) {
351241

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);
354244

355245
relationQueries.push(this.buildField(relatedModel, multiple, undefined, ignoreModels, name));
356246
}
@@ -369,7 +259,9 @@ export default class QueryBuilder {
369259
* @returns {boolean}
370260
*/
371261
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) {
373265
return true;
374266
}
375267

@@ -380,13 +272,4 @@ export default class QueryBuilder {
380272
private shouldModelBeIgnored (model: Model, ignoreModels: Array<Model>): boolean {
381273
return ignoreModels.find((m) => m.singularName === model.singularName) !== undefined;
382274
}
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-
}
392275
}

src/transformer.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { Data, Field } from './interfaces';
2+
import Model from './model';
3+
import Context from './context';
4+
import { downcaseFirstLetter } from './utils';
5+
const inflection = require('inflection');
6+
7+
export default class Transformer {
8+
/**
9+
* Transforms outgoing data. Use for variables param.
10+
*
11+
* Omits relations and some fields.
12+
*
13+
* @param model
14+
* @param {Data} data
15+
* @returns {Data}
16+
*/
17+
public static transformOutgoingData (model: Model, data: Data): Data {
18+
const relations: Map<string, Field> = model.getRelations();
19+
const returnValue: Data = {};
20+
const context = Context.getInstance();
21+
22+
Object.keys(data).forEach((key) => {
23+
const value = data[key];
24+
25+
// Ignore hasMany/One connections, empty fields and internal fields ($)
26+
if ((!relations.has(key) || relations.get(key) instanceof context.components.BelongsTo) &&
27+
!key.startsWith('$') && value !== null) {
28+
29+
if (value instanceof Array) {
30+
// Iterate over all fields and transform them if value is an array
31+
const arrayModel = context.getModel(inflection.singularize(key));
32+
returnValue[key] = value.map((v) => this.transformOutgoingData(arrayModel || model, v));
33+
} else if (typeof value === 'object' && context.getModel(inflection.singularize(key), true)) {
34+
// Value is a record, transform that too
35+
const relatedModel = context.getModel(inflection.singularize(key));
36+
returnValue[key] = this.transformOutgoingData(relatedModel || model, value);
37+
} else {
38+
// In any other case just let the value be what ever it is
39+
returnValue[key] = value;
40+
}
41+
}
42+
});
43+
44+
return returnValue;
45+
}
46+
47+
/**
48+
* Transforms a set of incoming data to the format vuex-orm requires.
49+
*
50+
* @param {Data | Array<Data>} data
51+
* @param model
52+
* @param mutation required to transform something like `disableUserAddress` to the actual model name.
53+
* @param {boolean} recursiveCall
54+
* @returns {Data}
55+
*/
56+
public static transformIncomingData (data: Data | Array<Data>, model: Model, mutation: boolean = false, recursiveCall: boolean = false): Data {
57+
let result: Data = {};
58+
const context = Context.getInstance();
59+
60+
if (!recursiveCall) {
61+
context.logger.group('Transforming incoming data');
62+
context.logger.log('Raw data:', data);
63+
}
64+
65+
if (data instanceof Array) {
66+
result = data.map(d => this.transformIncomingData(d, model, mutation, true));
67+
} else {
68+
Object.keys(data).forEach((key) => {
69+
if (data[key] !== undefined && data[key] !== null) {
70+
if (data[key] instanceof Object) {
71+
const localModel: Model = context.getModel(key, true) || model;
72+
73+
if (data[key].nodes) {
74+
result[inflection.pluralize(key)] = this.transformIncomingData(data[key].nodes,
75+
localModel, mutation, true);
76+
} else {
77+
let newKey = key;
78+
79+
if (mutation && !recursiveCall) {
80+
newKey = data[key].nodes ? localModel.pluralName : localModel.singularName;
81+
newKey = downcaseFirstLetter(newKey);
82+
}
83+
84+
result[newKey] = this.transformIncomingData(data[key], localModel, mutation, true);
85+
}
86+
} else if (model.fieldIsNumber(model.fields.get(key))) {
87+
result[key] = parseFloat(data[key]);
88+
} else if (key.endsWith('Type') && model.isTypeFieldOfPolymorphRelation(key)) {
89+
result[key] = inflection.pluralize(downcaseFirstLetter(data[key]));
90+
} else {
91+
result[key] = data[key];
92+
}
93+
}
94+
});
95+
}
96+
97+
if (!recursiveCall) {
98+
context.logger.log('Transformed data:', result);
99+
context.logger.groupEnd();
100+
} else {
101+
result['$isPersisted'] = true;
102+
}
103+
104+
// MAke sure this is really a plain JS object. We had some issues in testing here.
105+
return JSON.parse(JSON.stringify(result));
106+
}
107+
}

0 commit comments

Comments
 (0)