Skip to content

Commit f6a66f6

Browse files
mernxlnodkz
authored andcommitted
feat: add createMany Resolver (thanks @mernxl)
* feat(resolvers): add `createMany` resolver * test(discriminators): add test cases and optimise tests * doc: update README.md with newly added `createMany` resolver info
1 parent b036bf4 commit f6a66f6

15 files changed

+635
-93
lines changed

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ schemaComposer.Query.addFields({
105105
});
106106

107107
schemaComposer.Mutation.addFields({
108-
userCreate: UserTC.getResolver('createOne'),
108+
userCreateOne: UserTC.getResolver('createOne'),
109+
userCreateMany: UserTC.getResolver('createMany'),
109110
userUpdateById: UserTC.getResolver('updateById'),
110111
userUpdateOne: UserTC.getResolver('updateOne'),
111112
userUpdateMany: UserTC.getResolver('updateMany'),
@@ -374,17 +375,18 @@ fragment fullImageData on EmbeddedImage {
374375
```
375376

376377
### Access and modify mongoose doc before save
377-
This library provides some amount of ready resolvers for fetch and update data which was mentioned above. And you can [create your own resolver](https://github.com/graphql-compose/graphql-compose) of course. However you can find that add some actions or light modifications of mongoose document directly before save at existing resolvers appears more simple than create new resolver. Some of resolvers accepts *before save hook* wich can be provided in *resolver params* as param named `beforeRecordMutate`. This hook allows to have access and modify mongoose document before save. The resolvers which supports this hook are:
378+
This library provides some amount of ready resolvers for fetch and update data which was mentioned above. And you can [create your own resolver](https://github.com/graphql-compose/graphql-compose) of course. However you can find that add some actions or light modifications of mongoose document directly before save at existing resolvers appears more simple than create new resolver. Some of resolvers accepts *before save hook* which can be provided in *resolver params* as param named `beforeRecordMutate`. This hook allows to have access and modify mongoose document before save. The resolvers which supports this hook are:
378379

379380
* createOne
381+
* createMany
380382
* removeById
381383
* removeOne
382384
* updateById
383385
* updateOne
384386

385-
The protype of before save hook:
387+
The prototype of before save hook:
386388
```js
387-
(record: mixed, rp: ExtendedResolveParams) => Promise<*>,
389+
(doc: mixed, rp: ExtendedResolveParams) => Promise<*>,
388390
```
389391

390392
The typical implementation may be like this:
@@ -434,6 +436,7 @@ function adminAccess(resolvers) {
434436
// and wrap the resolvers
435437
schemaComposer.Mutation.addFields({
436438
createResource: ResourceTC.getResolver('createOne'),
439+
createResources: ResourceTC.getResolver('createMany'),
437440
...adminAccess({
438441
updateResource: ResourceTC.getResolver('updateById'),
439442
removeResource: ResourceTC.getResolver('removeById'),
@@ -522,6 +525,9 @@ export type typeConverterResolversOpts = {
522525
createOne?: false | {
523526
record?: recordHelperArgsOpts | false,
524527
},
528+
createMany?: false | {
529+
records?: recordHelperArgsOpts | false,
530+
},
525531
count?: false | {
526532
filter?: filterHelperArgsOpts | false,
527533
},

src/__tests__/composeWithMongooseDiscriminators-test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,21 @@ describe('composeWithMongooseDiscriminators ->', () => {
7272
expect(createOneRecordArgTC.isRequired('name')).toBe(true);
7373
expect(createOneRecordArgTC.hasField('friends')).toBe(false);
7474
});
75+
76+
it('should pass down records opts to createMany resolver', () => {
77+
const typeComposer = composeWithMongooseDiscriminators(CharacterModel, {
78+
resolvers: {
79+
createMany: {
80+
records: {
81+
removeFields: ['friends'],
82+
requiredFields: ['name'],
83+
},
84+
},
85+
},
86+
});
87+
const createManyRecordsArgTC = typeComposer.getResolver('createMany').getArgTC('records');
88+
expect(createManyRecordsArgTC.isRequired('name')).toBe(true);
89+
expect(createManyRecordsArgTC.hasField('friends')).toBe(false);
90+
});
7591
});
7692
});

src/composeWithMongoose.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ export type TypeConverterResolversOpts = {
101101
| {
102102
record?: RecordHelperArgsOpts | false,
103103
},
104+
createMany?:
105+
| false
106+
| {
107+
records?: RecordHelperArgsOpts | false,
108+
},
104109
count?:
105110
| false
106111
| {

src/discriminators/__tests__/prepareBaseResolvers-test.js

Lines changed: 70 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,108 @@
11
/* @flow */
22

33
import { graphql } from 'graphql-compose';
4-
import { getCharacterModels } from '../__mocks__/characterModels';
54
import { composeWithMongooseDiscriminators } from '../../composeWithMongooseDiscriminators';
5+
import { getCharacterModels } from '../__mocks__/characterModels';
66

77
const { CharacterModel } = getCharacterModels('type');
88

99
const CharacterDTC = composeWithMongooseDiscriminators(CharacterModel);
10+
const DKeyFieldName = CharacterDTC.getDKey();
11+
const DKeyETC = CharacterDTC.getDKeyETC();
12+
const DInterfaceTC = CharacterDTC.getDInterface();
1013

1114
describe('prepareBaseResolvers()', () => {
1215
describe('setDKeyEnumOnITCArgs()', () => {
13-
const resolversWithFilterAndRecordArgs = [];
14-
const resolversWithFilterArgsOnly = [];
15-
const resolversWithRecordArgsOnly = [];
16-
const resolversWithNoInterestArgs = [];
17-
const interestArgs = ['filter', 'record'];
16+
const resolversWithFilterArgs = [];
17+
const resolversWithRecordArgs = [];
18+
const resolversWithRecordsArgs = [];
19+
const interestArgs = ['filter', 'record', 'records'];
1820

1921
beforeAll(() => {
2022
const resolvers = CharacterDTC.getResolvers(); // map
2123

2224
resolvers.forEach(resolver => {
23-
if (resolver.hasArg(interestArgs[0]) && resolver.hasArg(interestArgs[1])) {
24-
resolversWithFilterAndRecordArgs.push(resolver);
25-
} else if (!(resolver.hasArg(interestArgs[0]) && resolver.hasArg(interestArgs[1]))) {
26-
resolversWithNoInterestArgs.push(resolver);
27-
} else if (resolver.hasArg(interestArgs[0]) && !resolver.hasArg(interestArgs[1])) {
28-
resolversWithFilterArgsOnly.push(resolver);
29-
} else if (!resolver.hasArg(interestArgs[0]) && resolver.hasArg(interestArgs[1])) {
30-
resolversWithRecordArgsOnly.push(resolver);
25+
const argNames = resolver.getArgNames();
26+
27+
for (const argName of argNames) {
28+
if (argName === interestArgs[0]) {
29+
resolversWithFilterArgs.push(resolver);
30+
}
31+
if (argName === interestArgs[1]) {
32+
resolversWithRecordArgs.push(resolver);
33+
}
34+
if (argName === interestArgs[2]) {
35+
resolversWithRecordsArgs.push(resolver);
36+
}
3137
}
3238
});
3339
});
3440

35-
it('should set type to DKeyEnum on DKey field on filter and record args', () => {
36-
for (const resolver of resolversWithFilterAndRecordArgs) {
37-
for (const arg of interestArgs) {
38-
expect(resolver.getArgTC(arg).getFieldType(CharacterDTC.getDKey())).toEqual(
39-
CharacterDTC.getDKeyETC().getType()
40-
);
41-
}
42-
}
43-
});
44-
45-
it('should set type to DKeyEnum on DKey field only on filter args', () => {
46-
for (const resolver of resolversWithFilterArgsOnly) {
41+
it('should set DKey field type to DKeyETC on filter args', () => {
42+
for (const resolver of resolversWithFilterArgs) {
4743
expect(interestArgs[0]).toEqual('filter');
48-
expect(resolver.getArgTC(interestArgs[0]).getFieldType(CharacterDTC.getDKey())).toEqual(
49-
CharacterDTC.getDKeyETC().getType()
50-
);
51-
expect(resolver.getArgTC(interestArgs[1]).getFieldType(CharacterDTC.getDKey())).not.toEqual(
44+
expect(resolver.getArgTC(interestArgs[0]).getFieldConfig(DKeyFieldName).type).toEqual(
5245
CharacterDTC.getDKeyETC().getType()
5346
);
5447
}
5548
});
5649

57-
it('should set type to DKeyEnum on DKey field only on record args', () => {
58-
for (const resolver of resolversWithFilterArgsOnly) {
50+
it('should set DKey field type to DKeyETC on record args', () => {
51+
for (const resolver of resolversWithRecordArgs) {
5952
expect(interestArgs[1]).toEqual('record');
60-
expect(resolver.getArgTC(interestArgs[1]).getFieldType(CharacterDTC.getDKey())).toEqual(
61-
CharacterDTC.getDKeyETC().getType()
62-
);
63-
expect(resolver.getArgTC(interestArgs[0]).getFieldType(CharacterDTC.getDKey())).not.toEqual(
64-
CharacterDTC.getDKeyETC().getType()
65-
);
53+
if (resolver.name === 'createOne') {
54+
expect(resolver.getArgTC(interestArgs[1]).getFieldConfig(DKeyFieldName).type).toEqual(
55+
graphql.GraphQLNonNull(DKeyETC.getType())
56+
);
57+
} else {
58+
expect(resolver.getArgTC(interestArgs[1]).getFieldConfig(DKeyFieldName).type).toEqual(
59+
DKeyETC.getType()
60+
);
61+
}
6662
}
6763
});
6864

69-
it('should NOT set type to DKeyEnum on DKey as filter and record not found args', () => {
70-
for (const resolver of resolversWithFilterArgsOnly) {
71-
for (const arg of interestArgs) {
72-
expect(resolver.hasArg(arg)).toBeFalsy();
73-
}
65+
it('should set DKey field type to DKeyETC on records args', () => {
66+
for (const resolver of resolversWithRecordsArgs) {
67+
expect(interestArgs[2]).toEqual('records');
68+
expect(resolver.getArgTC(interestArgs[2]).getFieldConfig(DKeyFieldName).type).toEqual(
69+
graphql.GraphQLNonNull(DKeyETC.getType())
70+
);
7471
}
7572
});
7673
});
7774

78-
it('should set resolver type to DInterface List, findMany', () => {
79-
expect(CharacterDTC.getResolver('findMany').getType()).toEqual(
80-
graphql.GraphQLList(CharacterDTC.getDInterface().getType())
81-
);
75+
describe('createOne: Resolver', () => {
76+
const resolver = CharacterDTC.getResolver('createOne');
77+
it('should set resolver record field type to DInterface', () => {
78+
expect(resolver.getTypeComposer().getFieldType('record')).toEqual(DInterfaceTC.getType());
79+
});
8280
});
8381

84-
it('should set resolver type to DInterface List, findByIds', () => {
85-
expect(CharacterDTC.getResolver('findByIds').getType()).toEqual(
86-
graphql.GraphQLList(CharacterDTC.getDInterface().getType())
87-
);
82+
describe('createMany: Resolver', () => {
83+
const resolver = CharacterDTC.getResolver('createMany');
84+
it('should set resolver records field type to NonNull Plural DInterface', () => {
85+
expect(resolver.getTypeComposer().getFieldType('records')).toEqual(
86+
new graphql.GraphQLNonNull(graphql.GraphQLList(DInterfaceTC.getType()))
87+
);
88+
});
89+
});
90+
91+
describe('findById: Resolver', () => {
92+
const resolver = CharacterDTC.getResolver('findByIds');
93+
it('should set resolver type to DInterface List', () => {
94+
expect(resolver.getType()).toEqual(
95+
graphql.GraphQLList(CharacterDTC.getDInterface().getType())
96+
);
97+
});
98+
});
99+
100+
describe('findMany: Resolver', () => {
101+
it('should set resolver type to DInterface List', () => {
102+
expect(CharacterDTC.getResolver('findMany').getType()).toEqual(
103+
graphql.GraphQLList(DInterfaceTC.getType())
104+
);
105+
});
88106
});
89107

90108
it('should set resolver type to DInterface, findOne', () => {
@@ -99,14 +117,6 @@ describe('prepareBaseResolvers()', () => {
99117
);
100118
});
101119

102-
it('should set resolver record field type to DInterface, createOne', () => {
103-
expect(
104-
CharacterDTC.getResolver('createOne')
105-
.getTypeComposer()
106-
.getFieldType('record')
107-
).toEqual(CharacterDTC.getDInterface().getType());
108-
});
109-
110120
it('should set resolver record field type to DInterface, updateOne', () => {
111121
expect(
112122
CharacterDTC.getResolver('updateOne')
@@ -131,7 +141,7 @@ describe('prepareBaseResolvers()', () => {
131141
).toEqual(CharacterDTC.getDInterface().getType());
132142
});
133143

134-
it('should set resolver record arg field, DKey to NonNull DKeyETC type, createOne', () => {
144+
it('should set DKey field type to NonNull(DKeyETC) on record arg, createOne', () => {
135145
expect(
136146
CharacterDTC.getResolver('createOne')
137147
.getArgTC('record')

src/discriminators/__tests__/prepareChildResolvers-test.js

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,99 @@
11
/* @flow */
22

3-
import { schemaComposer } from 'graphql-compose';
4-
import { getCharacterModels } from '../__mocks__/characterModels';
3+
import { schemaComposer, TypeComposer } from 'graphql-compose';
54
import { composeWithMongooseDiscriminators } from '../../composeWithMongooseDiscriminators';
5+
import { getCharacterModels } from '../__mocks__/characterModels';
66

7-
const { CharacterModel, PersonModel } = getCharacterModels('type');
7+
const DKeyFieldName = 'type';
8+
const { CharacterModel, PersonModel } = getCharacterModels(DKeyFieldName);
9+
10+
beforeAll(() => (CharacterModel: any).base.connect());
11+
afterAll(() => (CharacterModel: any).base.disconnect());
812

913
describe('prepareChildResolvers()', () => {
14+
describe('setQueryDKey()', () => {
15+
let PersonTC: TypeComposer;
16+
17+
beforeAll(() => {
18+
PersonTC = composeWithMongooseDiscriminators(CharacterModel).discriminator(PersonModel);
19+
});
20+
21+
beforeEach(async () => {
22+
await PersonModel.remove({});
23+
});
24+
25+
it('should set DKey on createOne', async () => {
26+
const res = await PersonTC.getResolver('createOne').resolve({
27+
args: {
28+
record: { name: 'Agent 007', dob: 124343 },
29+
},
30+
});
31+
32+
expect(res.record[DKeyFieldName]).toBe(PersonModel.modelName);
33+
});
34+
35+
it('should set DKey on createMany', async () => {
36+
const res = await PersonTC.getResolver('createMany').resolve({
37+
args: {
38+
records: [{ name: 'Agent 007', dob: 124343 }, { name: 'Agent 007', dob: 124343 }],
39+
},
40+
});
41+
42+
expect(res.records[0][DKeyFieldName]).toBe(PersonModel.modelName);
43+
expect(res.records[1][DKeyFieldName]).toBe(PersonModel.modelName);
44+
});
45+
});
46+
47+
describe('hideDKey()', () => {
48+
const resolversWithFilterArgs = [];
49+
const resolversWithRecordArgs = [];
50+
const resolversWithRecordsArgs = [];
51+
const interestArgs = ['filter', 'record', 'records'];
52+
53+
beforeAll(() => {
54+
const PersonTC = composeWithMongooseDiscriminators(CharacterModel).discriminator(PersonModel);
55+
56+
const resolvers = PersonTC.getResolvers();
57+
58+
resolvers.forEach(resolver => {
59+
const argNames = resolver.getArgNames();
60+
61+
for (const argName of argNames) {
62+
if (argName === interestArgs[0]) {
63+
resolversWithFilterArgs.push(resolver);
64+
}
65+
if (argName === interestArgs[1]) {
66+
resolversWithRecordArgs.push(resolver);
67+
}
68+
if (argName === interestArgs[2]) {
69+
resolversWithRecordsArgs.push(resolver);
70+
}
71+
}
72+
});
73+
});
74+
75+
it('should hide DKey field on filter args', () => {
76+
for (const resolver of resolversWithFilterArgs) {
77+
expect(interestArgs[0]).toEqual('filter');
78+
expect(resolver.getArgTC(interestArgs[0]).hasField(DKeyFieldName)).toBeFalsy();
79+
}
80+
});
81+
82+
it('should hide DKey field on record args', () => {
83+
for (const resolver of resolversWithRecordArgs) {
84+
expect(interestArgs[1]).toEqual('record');
85+
expect(resolver.getArgTC(interestArgs[1]).hasField(DKeyFieldName)).toBeFalsy();
86+
}
87+
});
88+
89+
it('should hide DKey field on records args', () => {
90+
for (const resolver of resolversWithRecordsArgs) {
91+
expect(interestArgs[2]).toEqual('records');
92+
expect(resolver.getArgTC(interestArgs[2]).hasField(DKeyFieldName)).toBeFalsy();
93+
}
94+
});
95+
});
96+
1097
describe('copyResolverArgTypes()', () => {
1198
afterAll(() => {
1299
schemaComposer.clear();

0 commit comments

Comments
 (0)