Skip to content

Commit 5b6f041

Browse files
committed
Add modelConverter, which converts mongoose model by provided options to TypeComposer.
1 parent bf26926 commit 5b6f041

File tree

7 files changed

+420
-100
lines changed

7 files changed

+420
-100
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/* eslint-disable no-unused-expressions */
2+
3+
import { expect } from 'chai';
4+
import { UserModel } from '../__mocks__/userModel.js';
5+
import { mongooseModelToTypeComposer as mm2tc } from '../modelConverter';
6+
7+
import TypeComposer from '../../../graphql-compose/src/typeComposer';
8+
import InputTypeComposer from '../../../graphql-compose/src/inputTypeComposer';
9+
10+
11+
describe('modelConverter', () => {
12+
describe('mongooseModelToTypeComposer()', () => {
13+
describe('basics', () => {
14+
it('should return TypeComposer', () => {
15+
expect(mm2tc(UserModel)).instanceof(TypeComposer);
16+
expect(mm2tc(UserModel, { name: 'Ok' })).instanceof(TypeComposer);
17+
});
18+
19+
it('should set type name from model or opts.name', () => {
20+
expect(mm2tc(UserModel).getTypeName())
21+
.equal(UserModel.modelName);
22+
expect(mm2tc(UserModel, { name: 'Ok' }).getTypeName())
23+
.equal('Ok');
24+
});
25+
26+
it('should set description from opts.description', () => {
27+
expect(mm2tc(UserModel, { description: 'This is model from mongoose' }).getDescription())
28+
.equal('This is model from mongoose');
29+
});
30+
31+
it('should get fields from mongoose model', () => {
32+
const tc = mm2tc(UserModel);
33+
expect(tc.getFields()).to.contain.keys(['_id', 'name', 'gender', 'age']);
34+
});
35+
});
36+
37+
describe('filterFields()', () => {
38+
it('should proceed opts.fields.remove', () => {
39+
const tc = mm2tc(UserModel, {
40+
fields: {
41+
remove: ['name', 'gender'],
42+
},
43+
});
44+
expect(tc.getFields()).to.not.contain.keys(['name', 'gender']);
45+
expect(tc.getFields()).to.contain.keys(['_id', 'age']);
46+
});
47+
48+
it('should proceed opts.fields.only', () => {
49+
const tc = mm2tc(UserModel, {
50+
fields: {
51+
only: ['name', 'gender'],
52+
},
53+
});
54+
expect(tc.getFields()).to.have.all.keys(['name', 'gender']);
55+
});
56+
});
57+
58+
describe('createInputType()', () => {
59+
it('should be availiable InputTypeComposer', () => {
60+
const inputTypeComposer = mm2tc(UserModel).getInputTypeComposer();
61+
expect(inputTypeComposer).instanceof(InputTypeComposer);
62+
});
63+
64+
it('should set type name opts.inputType.name', () => {
65+
const inputTypeComposer = mm2tc(UserModel, {
66+
inputType: {
67+
name: 'GreatUserInput',
68+
},
69+
}).getInputTypeComposer();
70+
71+
expect(inputTypeComposer.getTypeName())
72+
.equal('GreatUserInput');
73+
});
74+
75+
it('should set description from opts.inputType.name', () => {
76+
const inputTypeComposer = mm2tc(UserModel, {
77+
inputType: {
78+
description: 'type for input data',
79+
},
80+
}).getInputTypeComposer();
81+
82+
expect(inputTypeComposer.getDescription())
83+
.equal('type for input data');
84+
});
85+
86+
it('should proceed opts.inputType.fields.remove', () => {
87+
const inputTypeComposer = mm2tc(UserModel, {
88+
inputType: {
89+
fields: {
90+
remove: ['name', 'gender'],
91+
},
92+
},
93+
}).getInputTypeComposer();
94+
95+
expect(inputTypeComposer.getFields()).to.not.contain.keys(['name', 'gender']);
96+
expect(inputTypeComposer.getFields()).to.contain.keys(['_id', 'age']);
97+
});
98+
99+
it('should proceed opts.inputType.fields.only', () => {
100+
const inputTypeComposer = mm2tc(UserModel, {
101+
inputType: {
102+
fields: {
103+
only: ['name', 'gender'],
104+
},
105+
},
106+
}).getInputTypeComposer();
107+
108+
expect(inputTypeComposer.getFields()).to.have.all.keys(['name', 'gender']);
109+
});
110+
111+
it('should proceed opts.inputType.fields.required', () => {
112+
const inputTypeComposer = mm2tc(UserModel, {
113+
inputType: {
114+
fields: {
115+
required: ['name', 'gender'],
116+
},
117+
},
118+
}).getInputTypeComposer();
119+
120+
expect(inputTypeComposer.isFieldRequired('name')).to.be.true;
121+
expect(inputTypeComposer.isFieldRequired('gender')).to.be.true;
122+
expect(inputTypeComposer.isFieldRequired('age')).to.be.false;
123+
});
124+
});
125+
126+
describe('createResolvers()', () => {
127+
it('should not be called if opts.resolvers === false', () => {
128+
const tc = mm2tc(UserModel, { resolvers: false });
129+
expect(tc.getResolvers().getKeys()).is.empty;
130+
});
131+
132+
it('should be called if opts.resolvers not exists or has value', () => {
133+
const tc = mm2tc(UserModel);
134+
expect(tc.getResolvers().getKeys()).is.not.empty;
135+
const tc2 = mm2tc(UserModel, { resolvers: {} });
136+
expect(tc2.getResolvers().getKeys()).is.not.empty;
137+
});
138+
139+
it('should not provide resolver if opts.resolvers.[resolverName] === false', () => {
140+
const tc2 = mm2tc(UserModel, {
141+
resolvers: {
142+
findById: false,
143+
removeById: false,
144+
findMany: {},
145+
updateOne: {
146+
some: 123,
147+
},
148+
},
149+
});
150+
const resolverKeys = tc2.getResolvers().getKeys();
151+
expect(resolverKeys).to.not.include('findById');
152+
expect(resolverKeys).to.not.include('removeById');
153+
expect(resolverKeys).include.members(['findMany', 'updateOne', 'updateMany']);
154+
});
155+
});
156+
});
157+
158+
describe('complex situations', () => {
159+
it('required input fields, should be passed down to resolvers', () => {
160+
const typeComposer = mm2tc(UserModel, {
161+
inputType: {
162+
fields: {
163+
required: ['age'],
164+
},
165+
},
166+
});
167+
const filterArgInFindOne = typeComposer.getResolver('findOne').getArg('filter');
168+
const inputConposer = new InputTypeComposer(filterArgInFindOne.type);
169+
expect(inputConposer.isFieldRequired('age')).to.be.true;
170+
});
171+
});
172+
});

src/definition.js

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export type MonooseModelIndex = [
1919
];
2020

2121
export type MongooseModelT = {
22+
modelName: string,
2223
schema: MongooseModelSchemaT,
2324
create(doc: Object | Object[]): Promise,
2425
findOne(conditions: ?Object, projection?: Object): MongooseQuery,
@@ -99,8 +100,8 @@ export type filterHelperArgsOpts = {
99100
filterTypeName?: string,
100101
isRequired?: boolean,
101102
onlyIndexed?: boolean,
102-
model?: MongooseModelT,
103103
requiredFields?: string | string[],
104+
model?: MongooseModelT,
104105
};
105106

106107
export type sortHelperArgsOpts = {
@@ -109,9 +110,9 @@ export type sortHelperArgsOpts = {
109110

110111
export type inputHelperArgsOpts = {
111112
inputTypeName?: string,
113+
isRequired?: boolean,
112114
removeFields?: string[],
113115
requiredFields?: string[],
114-
isRequired?: boolean,
115116
};
116117

117118
export type limitHelperArgsOpts = {
@@ -124,3 +125,75 @@ export type genResolverOpts = {
124125
input?: inputHelperArgsOpts,
125126
limit?: limitHelperArgsOpts,
126127
}
128+
129+
130+
export type typeConverterOpts = {
131+
name?: string,
132+
description?: string,
133+
fields?: {
134+
only?: string[],
135+
// rename?: { [oldName: string]: string },
136+
remove?: string[],
137+
},
138+
inputType?: typeConverterInputTypeOpts,
139+
resolvers?: false | typeConverterResolversOpts,
140+
};
141+
142+
export type typeConverterInputTypeOpts = {
143+
name?: string,
144+
description?: string,
145+
fields?: {
146+
only?: string[],
147+
remove?: string[],
148+
required?: string[]
149+
},
150+
};
151+
152+
export type typeConverterResolversOpts = {
153+
findById?: false,
154+
findByIds?: false | {
155+
limit?: limitHelperArgsOpts | false,
156+
sort?: sortHelperArgsOpts | false,
157+
},
158+
findOne?: false | {
159+
filter?: filterHelperArgsOpts | false,
160+
sort?: sortHelperArgsOpts | false,
161+
skip?: false,
162+
},
163+
findMany?: false | {
164+
filter?: filterHelperArgsOpts | false,
165+
sort?: sortHelperArgsOpts | false,
166+
limit?: limitHelperArgsOpts | false,
167+
skip?: false,
168+
},
169+
updateById?: false | {
170+
input?: inputHelperArgsOpts | false,
171+
},
172+
updateOne?: false | {
173+
input?: inputHelperArgsOpts | false,
174+
filter?: filterHelperArgsOpts | false,
175+
sort?: sortHelperArgsOpts | false,
176+
skip?: false,
177+
},
178+
updateMany?: false | {
179+
input?: inputHelperArgsOpts | false,
180+
filter?: filterHelperArgsOpts | false,
181+
sort?: sortHelperArgsOpts | false,
182+
limit?: limitHelperArgsOpts | false,
183+
skip?: false,
184+
},
185+
removeById?: false,
186+
removeOne?: false | {
187+
filter?: filterHelperArgsOpts | false,
188+
sort?: sortHelperArgsOpts | false,
189+
},
190+
removeMany?: false | {
191+
filter?: filterHelperArgsOpts | false,
192+
},
193+
createOne?: false | {
194+
input?: inputHelperArgsOpts | false,
195+
},
196+
count?: false | {
197+
filter?: filterHelperArgsOpts | false,
198+
},
199+
}

src/index.js

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,3 @@
1-
import { GraphQLSchema } from 'graphql';
2-
import compose from './compose';
3-
4-
function initGraphqlTypes() {
5-
// populate root types in Storage.Types
6-
getViewerType();
7-
getRootQueryType();
8-
getRootMutationType();
9-
10-
// now all types declared, we are ready to extend types
11-
resolveUnresolvedRefs();
12-
addAdditionalFields();
13-
}
14-
15-
function getSchema() {
16-
initGraphqlTypes();
17-
18-
const schemaConfig = { query: getRootQueryType() };
19-
20-
if (Storage.AdditionalFields.has('RootMutation')) {
21-
schemaConfig.mutation = getRootMutationType();
22-
}
23-
24-
return new GraphQLSchema(schemaConfig);
25-
}
26-
27-
28-
export {
29-
getSchema,
30-
};
1+
import { mongooseModelToTypeComposer } from './modelConverter';
312

3+
export default mongooseModelToTypeComposer;

src/metaApiProposal.js

Lines changed: 0 additions & 59 deletions
This file was deleted.

0 commit comments

Comments
 (0)