Skip to content

Commit 1a54aaf

Browse files
committed
feat(resolvers): now resolvers have typechecks for args param (it checks names and presence of required arguments)
1 parent d4f6cee commit 1a54aaf

38 files changed

+262
-179
lines changed

package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"graphql-compose-pagination": "^7.0.0"
3232
},
3333
"peerDependencies": {
34-
"graphql-compose": "^7.21.0",
34+
"graphql-compose": "^7.21.1",
3535
"mongoose": "^5.0.0 || ^4.4.0"
3636
},
3737
"devDependencies": {
@@ -46,12 +46,12 @@
4646
"eslint-plugin-import": "2.22.0",
4747
"eslint-plugin-prettier": "3.1.4",
4848
"graphql": "15.3.0",
49-
"graphql-compose": "7.21.0",
49+
"graphql-compose": "7.21.1",
5050
"graphql-compose-connection": "^7.0.0",
5151
"graphql-compose-pagination": "^7.0.0",
5252
"jest": "26.4.2",
5353
"mongodb-memory-server": "6.7.4",
54-
"mongoose": "5.10.4",
54+
"mongoose": "5.10.5",
5555
"prettier": "2.1.1",
5656
"request": "2.88.2",
5757
"rimraf": "3.0.2",

src/composeMongoose.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import type { Model, Document } from 'mongoose';
44
import { convertModelToGraphQL } from './fieldsConverter';
55
import { allResolvers } from './resolvers';
66
import MongoID from './types/MongoID';
7-
import { ArgsMap } from './resolvers/helpers';
87
import {
98
prepareFields,
109
createInputType,
@@ -27,13 +26,18 @@ export type ComposeMongooseOpts<TContext> = {
2726
};
2827

2928
export type GenerateResolverType<TDoc extends Document, TContext = any> = {
30-
// get all available resolver generators, then leave only 3rd arg – opts
29+
// Get all available resolver generators, then leave only 3rd arg – opts
3130
// because first two args will be attached via bind() method at runtime:
3231
// count = count.bind(undefined, model, tc);
33-
// TODO: explain infer
3432
[resolver in keyof typeof allResolvers]: <TSource = any>(
3533
opts?: Parameters<typeof allResolvers[resolver]>[2]
36-
) => typeof allResolvers[resolver] extends (...args: any) => Resolver<any, any, infer TArgs, any>
34+
) => // Also we should patch generics of the returned Resolver
35+
// attach TContext TDoc from the code which will bind at runtime
36+
// and allow user to attach TSource via generic at call
37+
// For this case we are using `extends infer` construction
38+
// it helps to extract any Generic from existed method
39+
// and then construct new combined return type
40+
typeof allResolvers[resolver] extends (...args: any) => Resolver<any, any, infer TArgs, any>
3741
? Resolver<TSource, TContext, TArgs, TDoc>
3842
: any;
3943
};

src/resolvers/__tests__/createMany-test.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,22 @@ describe('createMany() ->', () => {
5959
});
6060

6161
it('should rejected with Error if args.records is empty', async () => {
62-
const result = createMany(UserModel, UserTC).resolve({ args: {} });
62+
const result = createMany(UserModel, UserTC).resolve({
63+
// @ts-expect-error
64+
args: {},
65+
});
6366
await expect(result).rejects.toThrow(
6467
'User.createMany resolver requires args.records to be an Array and must contain at least one record'
6568
);
6669
});
6770

6871
it('should rejected with Error if args.records is not array', async () => {
69-
const result = createMany(UserModel, UserTC).resolve({ args: { records: {} } });
72+
const result = createMany(UserModel, UserTC).resolve({
73+
args: {
74+
// @ts-expect-error
75+
records: {},
76+
},
77+
});
7078
await expect(result).rejects.toThrow(
7179
'ser.createMany resolver requires args.records to be an Array and must contain at least one record'
7280
);

src/resolvers/__tests__/createOne-test.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ describe('createOne() ->', () => {
5151
});
5252

5353
it('should rejected with Error if args.record is empty', async () => {
54-
const result = createOne(UserModel, UserTC).resolve({ args: {} });
54+
const result = createOne(UserModel, UserTC).resolve({
55+
// @ts-expect-error
56+
args: {},
57+
});
5558
await expect(result).rejects.toThrow(
5659
'User.createOne resolver requires at least one value in args.record'
5760
);

src/resolvers/__tests__/dataLoaderMany-test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ describe('dataLoaderMany() ->', () => {
6262

6363
it('should return Error if args.ids contains incorrect ObjectId', async () => {
6464
// For more info see https://github.com/graphql/dataloader#loadmanykeys
65-
// var [ a, b, c ] = await myLoader.loadMany([ 'a', 'b', 'badkey' ]);
65+
// var [ a, b, c ] = await myLoader.loadMany([ 'a', 'b', 'badKey' ]);
6666
// // c instanceof Error
6767
const result = await dataLoaderMany(UserModel, UserTC).resolve({
6868
args: { _ids: [1] },

src/resolvers/__tests__/dataLoaderManyLean-test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ describe('dataLoaderManyLean() ->', () => {
6262

6363
it('should return Error if args.ids contains incorrect ObjectId', async () => {
6464
// For more info see https://github.com/graphql/dataloader#loadmanykeys
65-
// var [ a, b, c ] = await myLoader.loadMany([ 'a', 'b', 'badkey' ]);
65+
// var [ a, b, c ] = await myLoader.loadMany([ 'a', 'b', 'badKey' ]);
6666
// // c instanceof Error
6767
const result = await dataLoaderManyLean(UserModel, UserTC).resolve({
6868
args: { _ids: [1] },

src/resolvers/__tests__/findOne-test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ describe('findOne() ->', () => {
9898

9999
it('should return document if provided existed id', async () => {
100100
const result = await findOne(UserModel, UserTC).resolve({
101-
args: { id: user1._id },
101+
args: { filter: { _id: user1._id } },
102102
});
103103
expect(result.name).toBe(user1.name);
104104
});
@@ -122,14 +122,14 @@ describe('findOne() ->', () => {
122122

123123
it('should return mongoose document', async () => {
124124
const result = await findOne(UserModel, UserTC).resolve({
125-
args: { _id: user1._id },
125+
args: { filter: { _id: user1._id } },
126126
});
127127
expect(result).toBeInstanceOf(UserModel);
128128
});
129129

130130
it('should call `beforeQuery` method with non-executed `query` as arg', async () => {
131131
const result = await findOne(UserModel, UserTC).resolve({
132-
args: { _id: user1._id },
132+
args: { filter: { _id: user1._id } },
133133
beforeQuery(query: any, rp: ExtendedResolveParams) {
134134
expect(rp.model).toBe(UserModel);
135135
expect(rp.query).toHaveProperty('exec');

src/resolvers/__tests__/findOneLean-test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ describe('findOneLean() ->', () => {
9898

9999
it('should return document if provided existed id', async () => {
100100
const result = await findOneLean(UserModel, UserTC).resolve({
101-
args: { id: user1._id },
101+
args: { filter: { _id: user1._id } },
102102
});
103103
expect(result.name).toBe(user1.name);
104104
});
@@ -122,7 +122,7 @@ describe('findOneLean() ->', () => {
122122

123123
it('should return mongoose document', async () => {
124124
const result = await findOneLean(UserModel, UserTC).resolve({
125-
args: { _id: user1._id },
125+
args: { filter: { _id: user1._id } },
126126
});
127127
expect(result).not.toBeInstanceOf(UserModel);
128128
// should translate aliases fields
@@ -131,7 +131,7 @@ describe('findOneLean() ->', () => {
131131

132132
it('should call `beforeQuery` method with non-executed `query` as arg', async () => {
133133
const result = await findOneLean(UserModel, UserTC).resolve({
134-
args: { _id: user1._id },
134+
args: { filter: { _id: user1._id } },
135135
beforeQuery(query: any, rp: ExtendedResolveParams) {
136136
expect(rp.model).toBe(UserModel);
137137
expect(rp.query).toHaveProperty('lean');

src/resolvers/__tests__/removeById-test.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,10 @@ describe('removeById() ->', () => {
6060
});
6161

6262
it('should rejected with Error if args._id is empty', async () => {
63-
const result = removeById(UserModel, UserTC).resolve({ args: {} });
63+
const result = removeById(UserModel, UserTC).resolve({
64+
// @ts-expect-error
65+
args: {},
66+
});
6467
await expect(result).rejects.toThrow('User.removeById resolver requires args._id value');
6568
});
6669

src/resolvers/__tests__/removeMany-test.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,10 @@ describe('removeMany() ->', () => {
7878
});
7979

8080
it('should rejected with Error if args.filter is empty', async () => {
81-
const result = removeMany(UserModel, UserTC).resolve({ args: {} });
81+
const result = removeMany(UserModel, UserTC).resolve({
82+
// @ts-expect-error
83+
args: {},
84+
});
8285
await expect(result).rejects.toThrow(
8386
'User.removeMany resolver requires at least one value in args.filter'
8487
);

src/resolvers/__tests__/removeOne-test.ts

-2
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,9 @@ describe('removeOne() ->', () => {
119119
});
120120

121121
it('should remove document in database', async () => {
122-
const checkedName = 'nameForMongoDB';
123122
await removeOne(UserModel, UserTC).resolve({
124123
args: {
125124
filter: { _id: user1.id },
126-
input: { name: checkedName },
127125
},
128126
});
129127

src/resolvers/__tests__/updateById-test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ describe('updateById() ->', () => {
7575

7676
it('should rejected with Error if args._id is empty', async () => {
7777
const result = updateById(UserModel, UserTC).resolve({
78+
// @ts-expect-error
7879
args: { record: {} },
7980
});
8081
await expect(result).rejects.toThrow('User.updateById resolver requires args._id value');

src/resolvers/__tests__/updateMany-test.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,10 @@ describe('updateMany() ->', () => {
9292
});
9393

9494
it('should rejected with Error if args.record is empty', async () => {
95-
const result = updateMany(UserModel, UserTC).resolve({ args: {} });
95+
const result = updateMany(UserModel, UserTC).resolve({
96+
// @ts-expect-error
97+
args: {},
98+
});
9699
await expect(result).rejects.toThrow(
97100
'User.updateMany resolver requires at least one value in args.record'
98101
);

src/resolvers/__tests__/updateOne-test.ts

+20-6
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,10 @@ describe('updateOne() ->', () => {
9090
});
9191

9292
it('should rejected with Error if args.filter is empty', async () => {
93-
const result = updateOne(UserModel, UserTC).resolve({ args: {} });
93+
const result = updateOne(UserModel, UserTC).resolve({
94+
// @ts-expect-error
95+
args: {},
96+
});
9497
await expect(result).rejects.toThrow(
9598
'User.updateOne resolver requires at least one value in args.filter'
9699
);
@@ -135,7 +138,7 @@ describe('updateOne() ->', () => {
135138

136139
it('should return payload.record', async () => {
137140
const result = await updateOne(UserModel, UserTC).resolve({
138-
args: { filter: { _id: user1.id } },
141+
args: { filter: { _id: user1.id }, record: { name: 'abc' } },
139142
});
140143
expect(result.record.id).toBe(user1.id);
141144
});
@@ -156,7 +159,7 @@ describe('updateOne() ->', () => {
156159

157160
it('should return empty payload.error', async () => {
158161
const result = await updateOne(UserModel, UserTC).resolve({
159-
args: { filter: { _id: user1.id } },
162+
args: { filter: { _id: user1.id }, record: { name: 'abc' } },
160163
});
161164
expect(result.error).toEqual(undefined);
162165
});
@@ -200,12 +203,14 @@ describe('updateOne() ->', () => {
200203
const result1 = await updateOne(UserModel, UserTC).resolve({
201204
args: {
202205
filter: { relocation: true },
206+
record: { name: 'abc' },
203207
skip: 0,
204208
},
205209
});
206210
const result2 = await updateOne(UserModel, UserTC).resolve({
207211
args: {
208212
filter: { relocation: true },
213+
record: { name: 'abc' },
209214
skip: 1,
210215
},
211216
});
@@ -216,12 +221,14 @@ describe('updateOne() ->', () => {
216221
const result1 = await updateOne(UserModel, UserTC).resolve({
217222
args: {
218223
filter: { relocation: true },
224+
record: { name: 'abc' },
219225
sort: { _id: 1 },
220226
},
221227
});
222228
const result2 = await updateOne(UserModel, UserTC).resolve({
223229
args: {
224230
filter: { relocation: true },
231+
record: { name: 'abc' },
225232
sort: { _id: -1 },
226233
},
227234
});
@@ -232,6 +239,7 @@ describe('updateOne() ->', () => {
232239
const result = await updateOne(UserModel, UserTC).resolve({
233240
args: {
234241
filter: { _id: user1.id },
242+
record: { name: 'abc' },
235243
},
236244
projection: {
237245
record: {
@@ -240,21 +248,27 @@ describe('updateOne() ->', () => {
240248
},
241249
});
242250
expect(result.record.id).toBe(user1.id);
243-
expect(result.record.name).toBe(user1.name);
251+
expect(result.record.name).toBe('abc');
244252
expect(result.record.gender).toBe(user1.gender);
245253
});
246254

247255
it('should return mongoose document', async () => {
248256
const result = await updateOne(UserModel, UserTC).resolve({
249-
args: { filter: { _id: user1.id } },
257+
args: {
258+
filter: { _id: user1.id },
259+
record: { name: 'abc' },
260+
},
250261
});
251262
expect(result.record).toBeInstanceOf(UserModel);
252263
});
253264

254265
it('should call `beforeRecordMutate` method with founded `record` and `resolveParams` as args', async () => {
255266
let beforeMutationId;
256267
const result = await updateOne(UserModel, UserTC).resolve({
257-
args: { filter: { _id: user1.id } },
268+
args: {
269+
filter: { _id: user1.id },
270+
record: { name: 'abc' },
271+
},
258272
context: { ip: '1.1.1.1' },
259273
beforeRecordMutate: (record: any, rp: ExtendedResolveParams) => {
260274
beforeMutationId = record.id;

src/resolvers/connection.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import type {
66
ObjectTypeComposerFieldConfigMap,
77
} from 'graphql-compose';
88
import { getUniqueIndexes, extendByReversedIndexes, IndexT } from '../utils/getIndexesFromModel';
9-
import { ArgsMap } from './helpers';
109

1110
export type ConnectionResolverOpts<TContext = any> = _ConnectionSortMapOpts & {
1211
edgeFields?: ObjectTypeComposerFieldConfigMap<any, TContext>;
@@ -16,11 +15,20 @@ export type ConnectionResolverOpts<TContext = any> = _ConnectionSortMapOpts & {
1615
edgeTypeName?: string;
1716
};
1817

18+
type TArgs = {
19+
first?: number;
20+
after?: string;
21+
last?: number;
22+
before?: string;
23+
filter?: any;
24+
sort?: Record<string, any>;
25+
};
26+
1927
export function connection<TSource = any, TContext = any, TDoc extends Document = any>(
2028
model: Model<TDoc>,
2129
tc: ObjectTypeComposer<TDoc, TContext>,
2230
opts?: ConnectionResolverOpts<TContext>
23-
): Resolver<TSource, TContext, ArgsMap, TDoc> | undefined {
31+
): Resolver<TSource, TContext, TArgs, TDoc> | undefined {
2432
try {
2533
require.resolve('graphql-compose-connection');
2634
} catch (e) {

src/resolvers/count.ts

+8-10
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
import type { Resolver, ObjectTypeComposer } from 'graphql-compose';
22
import type { Model, Document } from 'mongoose';
3-
import {
4-
filterHelper,
5-
filterHelperArgs,
6-
prepareAliases,
7-
FilterHelperArgsOpts,
8-
ArgsMap,
9-
} from './helpers';
3+
import { filterHelper, filterHelperArgs, prepareAliases, FilterHelperArgsOpts } from './helpers';
104
import type { ExtendedResolveParams } from './index';
115
import { beforeQueryHelper } from './helpers/beforeQueryHelper';
126

@@ -15,11 +9,15 @@ export interface CountResolverOpts {
159
filter?: FilterHelperArgsOpts | false;
1610
}
1711

12+
type TArgs = {
13+
filter?: any;
14+
};
15+
1816
export function count<TSource = any, TContext = any, TDoc extends Document = any>(
1917
model: Model<TDoc>,
2018
tc: ObjectTypeComposer<TDoc, TContext>,
2119
opts?: CountResolverOpts
22-
): Resolver<TSource, TContext, ArgsMap, TDoc> {
20+
): Resolver<TSource, TContext, TArgs, TDoc> {
2321
if (!model || !model.modelName || !model.schema) {
2422
throw new Error('First arg for Resolver count() should be instance of Mongoose Model.');
2523
}
@@ -30,7 +28,7 @@ export function count<TSource = any, TContext = any, TDoc extends Document = any
3028

3129
const aliases = prepareAliases(model);
3230

33-
return tc.schemaComposer.createResolver({
31+
return tc.schemaComposer.createResolver<TSource, TArgs>({
3432
type: 'Int',
3533
name: 'count',
3634
kind: 'query',
@@ -55,5 +53,5 @@ export function count<TSource = any, TContext = any, TDoc extends Document = any
5553
return beforeQueryHelper(resolveParams);
5654
}
5755
}) as any,
58-
}) as any;
56+
});
5957
}

0 commit comments

Comments
 (0)