Skip to content

add an argument for one operation seems too complex #137

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
ziedHamdi opened this issue Nov 9, 2018 · 13 comments
Closed

add an argument for one operation seems too complex #137

ziedHamdi opened this issue Nov 9, 2018 · 13 comments

Comments

@ziedHamdi
Copy link

ziedHamdi commented Nov 9, 2018

Hi,

I have an entity called complaint that has many attachments in it. When editing a complaint, the user may want to remove some attachments, so I want to an argument that contains the list of removed attachments to the query.

To do that, I suuposed I cannot add an argument with wrapResolve, so I decided to nest the existing updateOne resolver and take the infos from it as follows:

const complaintUpdateOneResolver = ComplaintTC.getResolver('updateOne');
console.log("complaintUpdateOne: ", complaintUpdateOneResolver)
schemaComposer.rootMutation().addFields({
	complaintUpdateOne: new Resolver({
		name: complaintUpdateOneResolver.name,
		kind: complaintUpdateOneResolver.kind,
		type: complaintUpdateOneResolver.type,
		args: [...complaintUpdateOneResolver.args, {'attachmentsToRemove': ['String']}],
		resolve: async (params) => {
			console.log("args of complaintUpdateOne: ", complaintUpdateOneResolver.args)
			complaintUpdateOneResolver.resolve(params)
			const {args, context} = params;
			// do some other job here: remove attachment files from storage
		}
	},
    //...
})

I have an issue with this approach :

  • I don't know how to modify the record field: UpdateOneComplaintInput ???
complaintUpdateOne:  Resolver {
  name: 'updateOne',
  displayName: 'Complaint.updateOne',
  parent: null,
  kind: 'mutation',
  description:
   'Update one document: 1) Retrieve one document via findOne. 2) Apply updates to mongoose document. 3) Mongoose applies defaults, setters, hooks and validation. 4) And save it.',
  type: TypeComposer { gqType: UpdateOneComplaintPayload },
  args:
   { record: { type: UpdateOneComplaintInput! },
     filter:
      { type: FilterUpdateOneComplaintInput,
        description: 'Filter by fields' },
     sort: { type: [EnumTypeComposer] },
     skip: { type: 'Int' } },
  resolve: [Function: resolve] }

Do i have to create an new resolver from scratch for that? (I added a qestion on SO : https://stackoverflow.com/questions/53223092/how-to-intercept-graphql-compose-mongoose-schema-creation-to-add-an-argument-in)

@ziedHamdi
Copy link
Author

reading the source code, I arrived to the point where we can call composeWithMongoose(scheam, {schemaComposer:OwnSC}) (knowing that OwnSC is of type server/node_modules/graphql-compose/lib/SchemaComposer.d.ts)

In my point of view, the method composeWithMongoose shold be accompanied with a method eg. composeResolverWithMongoose( 'udpateOne', {opts:{args: {input:{...mongoosFields, additionalFields}}, resolve: (params) => {/*some stuff where we could combine the original behaviour with a specific one*/} } })

@nodkz
Copy link
Member

nodkz commented Nov 9, 2018

Do i have to create an new resolver from scratch for that?

Yep, better to create a new resolver.

I don't know how to modify the record field: UpdateOneComplaintInput ???

All Composers have a method clone(newName):

const clonnedITC = schemaComposer.getITC('UpdateOneComplaintInput').clone('ClonnedUpdateOneComplaintInput ');
clonnedITC .addFields({ ... });
const myResolver = new schemaComposer.Resolver({
   args: {
      input: clonnedITC 
   },
   ...
});

accompanied with a method eg. composeResolverWithMongoose

I dont think that it will be a good idea. Better to modify your Typevia helper methods as you need, instead of using configuration object:

const updateOneInputITC = ComplaintTC.getResolver('updateOne').getArgTC('input');

// in such way you modify type, moreover you also may remove fields
updateOneInputITC.addFields({ ... }); 
updateOneInputITC.removeField('password'); 

Assume that graphql-compose-mongoose just generate for you a cobblestone. And then you may change its form as you need. If need something special CRUD, feel freeto add it.

Take a look at TC, ITC, ETC, Resolver API: https://graphql-compose.github.io/docs/en/api-TypeComposer.html They have a buch of methods which helps programatically modify your types. Even better that it can be done via configuration object, like it did native graphql-js object constructor.

With graphql-compose you may create your own functions (templates) which may generate ,modify or extend types.It helps to greatly reduce the code.

@ziedHamdi
Copy link
Author

Hi @nodkz thanks for these great explanations, I was thinking about this task during the week-end (I just saw your answer right now). I was going to go to this direction of seeing how to modify the config after it was generated by mongoose. This is a great approach too (maybe it should be written in the docs to make it clear to everyone about the phylosophy of graphql-compose) : it's true it gives a lot more power to add the code you want and decorate/delegate it to the default implementations.

I would have expected a configuration layer as it's in the hibernate/JPA annotations where poeple can append many frameworks (like validation, logging, security, etc...), but it's ok for me as it is today, after all, the community can make public resolvers (that eventually compose with default ones) for each task.

Thanks again for your time and clarity, it's cool i've got out of this issue👍 :)

@ziedHamdi
Copy link
Author

ziedHamdi commented Nov 12, 2018

Hi nodekz,

I'm sorry, it's still not clear to me, what are the arguments needed for a new input: I wrote this code but it seam as if I have to register the cloned InputTC in the shemaComposer (he seams not to recognize it if I simply clone it without adding it anywhere)

console.log( "###### updateOneInputITC type:", ComplaintTC.getResolver('updateOne') )
const updateOneInputITC = ComplaintTC.getResolver('updateOne').getArgTC('record');
console.log( "###### attachments type:", updateOneInputITC.getFieldTC("attachments").getType() )
updateOneInputITC.addFields({
	newFields: [{
		removedAttachments: { // standard GraphQL like field definition
			type: updateOneInputITC.getFieldTC("attachments").clone('ComplaintComplaintRemovedAttachmentsInput ').getType(),
			resolve: (source) => {console.log( "removedAttachments source value: ", source ); return null},
		},
	}]
});

but it outputs:

###### updateOneInputITC type: Resolver {
  name: 'updateOne',
  displayName: 'Complaint.updateOne',
  parent: null,
  kind: 'mutation',
  description:
   'Update one document: 1) Retrieve one document via findOne. 2) Apply updates to mongoose document. 3) Mongoose applies defaults, setters, hooks and validation. 4) And save it.',
  type: TypeComposer { gqType: UpdateOneComplaintPayload },
  args:
   { record: { type: UpdateOneComplaintInput! },
     filter:
      { type: FilterUpdateOneComplaintInput,
        description: 'Filter by fields' },
     sort: { type: [EnumTypeComposer] },
     skip: { type: 'Int' } },
  resolve: [Function: resolve] }

###### attachments type: ComplaintComplaintAttachmentsInput
Error: UpdateOneComplaintInput.newFields provided incorrect InputType: '{"removedAttachments":{"type":"ComplaintComplaintRemovedAttachmentsInput "}}'
    at TypeMapper.convertInputFieldConfig (C:\Users\Zied\work\weally\server\node_modules\graphql-compose\lib\TypeMapper.js:498:17)
    at resolveInputConfigAsThunk (C:\Users\Zied\work\weally\server\node_modules\graphql-compose\lib\utils\configAsThunk.js:45:39)
    at C:\Users\Zied\work\weally\server\node_modules\graphql-compose\lib\utils\configAsThunk.js:58:22
    at Array.forEach (<anonymous>)
    at resolveInputConfigMapAsThunk (C:\Users\Zied\work\weally\server\node_modules\graphql-compose\lib\utils\configAsThunk.js:57:27)
    at gqType._typeConfig.fields (C:\Users\Zied\work\weally\server\node_modules\graphql-compose\lib\InputTypeComposer.js:146:64)
    at resolveThunk (C:\Users\Zied\work\weally\server\node_modules\graphql\type\definition.js:370:40)
    at GraphQLInputObjectType._defineFieldMap (C:\Users\Zied\work\weally\server\node_modules\graphql\type\definition.js:841:20)
    at GraphQLInputObjectType.getFields (C:\Users\Zied\work\weally\server\node_modules\graphql\type\definition.js:835:49)
    at typeMapReducer (C:\Users\Zied\work\weally\server\node_modules\graphql\type\schema.js:244:38)
    at typeMapReducer (C:\Users\Zied\work\weally\server\node_modules\graphql\type\schema.js:213:12)
    at Array.reduce (<anonymous>)
    at C:\Users\Zied\work\weally\server\node_modules\graphql\type\schema.js:237:36
    at Array.forEach (<anonymous>)
    at typeMapReducer (C:\Users\Zied\work\weally\server\node_modules\graphql\type\schema.js:232:51)
    at Array.reduce (<anonymous>)

In the docs you pointed me to, I don't understand the meaning of :

static create(
  opts: TypeAsString |
        ComposeInputObjectTypeConfig |
        GraphQLInputObjectType
): InputTypeComposer;

For example, how do I create a ComposeInputObjectTypeConfig ???

The same applies for https://graphql-compose.github.io/docs/en/api-InputTypeComposer.html#addfields

addFields(
  newFields: ComposeInputFieldConfigMap
): InputTypeComposer;

How do I create a ComposeInputFieldConfigMap??

Actually I noticed, even the simplest type creation doesn't work

updateOneInputITC.addFields({
	newFields: [{
		removedAttachments: { // standard GraphQL like field definition
			type: "String",
			resolve: (source) => {console.log( "removedAttachments source value: ", source ); source.removedAttachments = removedAttachments;return null},
		},
	}]
});

Is it because the type must be an input as mentioned here?
graphql/graphql-js#546

I saw in the source code the condition that must be met, I just don't know how to meet that and why using a type from another field (even a simple ont doesn't work):

function isInputType(type) {
  return type instanceof _graphql.GraphQLScalarType || type instanceof _graphql.GraphQLEnumType || type instanceof _graphql.GraphQLInputObjectType || type instanceof _graphql.GraphQLNonNull && isInputType(type.ofType) || type instanceof _graphql.GraphQLList && isInputType(type.ofType);
}
...

if (!(0, _is.isFunction)(fieldConfig.type)) {
        if (!isInputType(fieldConfig.type)) {
          throw new Error(`${typeName}.${fieldName} provided incorrect InputType: '${JSON.stringify(composeType)}'`);
        }

So how do I make my type a GraphQLInputObjectType ? and how do I make it an array of that input type???

p.s: please note that I naively thought that input type declaration will be the same as feld declaration described here: https://github.com/graphql-compose/graphql-compose

@ziedHamdi ziedHamdi reopened this Nov 12, 2018
@ziedHamdi
Copy link
Author

ziedHamdi commented Nov 12, 2018

I'm reusing the "attachments" type composer since both have the same structure (it's a complex type containing multiple fields here's its definition in mongoose, that def may evolve in time, and I want both to go parallel since removedAttachments is actually an array of attachments that were removed from the list: they have the same types by definition:

attachments: [{
		url: {type: String, required: true},
		name: String,
		mimeType: String,
		/**
		 * The attachmentId is used when the attachment is stored as a blob in mongodb
		 */
		attachmentId: Schema.Types.ObjectId
	}],

I also tried to use the same type


	newFields: [{
		removedAttachments: { // standard GraphQL like field definition
			type: updateOneInputITC.getFieldTC("attachments").getType(),

I also get an error:

Error: UpdateOneComplaintInput.newFields provided incorrect InputType: '{"removedAttachments":{"type":"ComplaintComplaintAttachmentsInput"}}'
    at TypeMapper.convertInputFieldConfig (C:\Users\Zied\work\weally\server\node_modules\graphql-compose\lib\TypeMapper.js:498:17)
    at resolveInputConfigAsThunk (C:\Users\Zied\work\weally\server\node_modules\graphql-compose\lib\utils\configAsThunk.js:45:39)

So I don't understand what is expected to be there since the type is already recognized byt the SC "ComplaintComplaintAttachmentsInput" as used in the fiels attachments

@nodkz
Copy link
Member

nodkz commented Nov 12, 2018

updateOneInputITC.addFields({
	newFields: [{
		removedAttachments: { // standard GraphQL like field definition
			type: updateOneInputITC.getFieldTC("attachments").clone('ComplaintComplaintRemovedAttachmentsInput ').getType(),
			resolve: (source) => {console.log( "removedAttachments source value: ", source ); return null},
		},
	}]
});

You cannot use array of js-object for field definition. You need to create InputType and then put it inside an array (if you want to make it plural):

newFields: [InputTypeComposer.create({
  name: '',
  fields: {}
})]

ComposeInputObjectTypeConfig is just Flowtype definition and can be found in the bottom of page or via search box on site: https://graphql-compose.github.io/docs/en/api-InputTypeComposer.html#composeinputobjecttypeconfig

@ziedHamdi
Copy link
Author

ziedHamdi commented Nov 12, 2018

Hi @nodkz ,

The code you've given me doesn't work (if you look to my answer, it already has that code), I tried that too, I even tried to use a simple String Input type but I still get that

"provided incorrect InputType"

error.

here's what the code you proposed outputs

console.log( "###### updateOneInputITC type:", ComplaintTC.getResolver('updateOne') )
const updateOneInputITC = ComplaintTC.getResolver('updateOne').getArgTC('record');
console.log( "###### attachments type:", updateOneInputITC.getFieldType("attachments") )
updateOneInputITC.addFields({
	newFields: [{
		removedAttachmentList: { // standard GraphQL like field definition
			type: updateOneInputITC.getFieldTC("attachments").clone('ComplaintComplaintRemovedAttachmentListInput ').getType(),
			resolve: (source) => {console.log( "removedAttachments source value: ", source ); return null},
		},
	}]
});

outputs this:

###### updateOneInputITC type: Resolver {
  name: 'updateOne',
  displayName: 'Complaint.updateOne',
  parent: null,
  kind: 'mutation',
  description:
   'Update one document: 1) Retrieve one document via findOne. 2) Apply updates to mongoose document. 3) Mongoose applies defaults, setters, hooks and validation. 4) And save it.',
  type: TypeComposer { gqType: UpdateOneComplaintPayload },
  args:
   { record: { type: UpdateOneComplaintInput! },
     filter:
      { type: FilterUpdateOneComplaintInput,
        description: 'Filter by fields' },
     sort: { type: [EnumTypeComposer] },
     skip: { type: 'Int' } },
  resolve: [Function: resolve] }
###### attachments type: [ComplaintComplaintAttachmentsInput]
C:\Users\Zied\work\weally\server\node_modules\graphql-compose\lib\TypeMapper.js:498
          throw new Error(`${typeName}.${fieldName} provided incorrect InputType: '${JSON.stringify(composeType)}'`);
          ^

Error: UpdateOneComplaintInput.newFields provided incorrect InputType: '{"removedAttachmentList":{"type":"ComplaintComplaintRemovedAttachmentListInput "}}'
    at TypeMapper.convertInputFieldConfig (C:\Users\Zied\work\weally\server\node_modules\graphql-compose\lib\TypeMapper.js:498:17)
    at resolveInputConfigAsThunk (C:\Users\Zied\work\weally\server\node_modules\graphql-compose\lib\utils\configAsThunk.js:45:39)
    at C:\Users\Zied\work\weally\server\node_modules\graphql-compose\lib\utils\configAsThunk.js:58:22
    at Array.forEach (<anonymous>)
    at resolveInputConfigMapAsThunk (C:\Users\Zied\work\weally\server\node_modules\graphql-compose\lib\utils\configAsThunk.js:57:27)
    at gqType._typeConfig.fields (C:\Users\Zied\work\weally\server\node_modules\graphql-compose\lib\InputTypeComposer.js:146:64)
    at resolveThunk (C:\Users\Zied\work\weally\server\node_modules\graphql\type\definition.js:370:40)
    at GraphQLInputObjectType._defineFieldMap (C:\Users\Zied\work\weally\server\node_modules\graphql\type\definition.js:841:20)
    at GraphQLInputObjectType.getFields (C:\Users\Zied\work\weally\server\node_modules\graphql\type\definition.js:835:49)
    at typeMapReducer (C:\Users\Zied\work\weally\server\node_modules\graphql\type\schema.js:244:38)
    at typeMapReducer (C:\Users\Zied\work\weally\server\node_modules\graphql\type\schema.js:213:12)
    at Array.reduce (<anonymous>)
    at C:\Users\Zied\work\weally\server\node_modules\graphql\type\schema.js:237:36
    at Array.forEach (<anonymous>)
    at typeMapReducer (C:\Users\Zied\work\weally\server\node_modules\graphql\type\schema.js:232:51)
    at Array.reduce (<anonymous>)

To circomvert the issue, I added a field called removedAttachments to my mongoose schema that I process in a wrapResolver for updateById. But I'd like to understand why the code above is not accepted by graphql-compose, I think it's a misuse of the library to add a field in schema to make thinks work...

p.s: I saw the flow declaration in the docs at the link you provided: https://graphql-compose.github.io/docs/en/api-InputTypeComposer.html#composeinputobjecttypeconfig , I'm maybe too new to js, but in my opinon this doc doesn't give a hint on how to create the field, to register in it in SchemaComposer, etc... I think a code example is worth it

@nodkz
Copy link
Member

nodkz commented Nov 12, 2018

Good docs site with examples takes too much time. Will improve will have the bandwidth. Thanks.

About the problem:

updateOneInputITC.addFields({
	newFields: [{ // < -------------- 🛑 cannot use a regular js object here
		removedAttachmentList: { // standard GraphQL like field definition
			type: updateOneInputITC.getFieldTC("attachments").clone('ComplaintComplaintRemovedAttachmentListInput ').getType(),
			resolve: (source) => {console.log( "removedAttachments source value: ", source ); return null},
		},
	}]
});

How I said before you need to create InputTypeComposer:

updateOneInputITC.addFields({
	newFields: [InputTypeComposer.create({ 
            name: 'MyNewInputTypeWhichShouldBeSomehowNamed',
            fields: {
		removedAttachmentList: { // standard GraphQL like field definition
			type: updateOneInputITC.getFieldTC("attachments").clone('ComplaintComplaintRemovedAttachmentListInput ').getType(),
			resolve: (source) => {console.log( "removedAttachments source value: ", source ); return null},
		},
           },
	})]
});

@ziedHamdi
Copy link
Author

ziedHamdi commented Nov 18, 2018

Hi @nodkz

Thanks for the explanations, I didn't get it right last tim. Also, sorry for the delay, I was in holidays this last week.

@ziedHamdi
Copy link
Author

Hi Pavel,

Srry I had some personal priorities, I wasn't able to test your solution (I added the field to the schema temporarily).

I'll mark it as a closed issue since I'm convinced you answered to my problem.
Thanks again,
Best regards

@ziedHamdi
Copy link
Author

ziedHamdi commented Nov 5, 2019

Hi Pavel,

The last time I needed this feature, I added it to the mongoose schema (the ugly solution)

Today I have this issue again, and I want to do it cleanly: I want to add two parameters to a create resolver (while the two params are not in my mongoose schema)
This is my code snippet

const createOneResolver = ComplaintActionTC.getResolver('createOne');
createOneResolver.getArgTC('record').makeOptional(['user']);
const createOneInputTC = createOneResolver.getArgTC('record')
createOneInputTC.addFields({
	newFields: [InputTypeComposer.create({
		name: 'complaintId',
		type: "String",
	}), InputTypeComposer.create({
		name: 'value',
		type: "String"
	})]
})

I get an error saying

Error: You must provide SchemaComposer instance as a second argument for `InputTypeComposer.create(typeDef, schemaComposer)`
    at Function.create (D:\Zied\work\weally\node_modules\graphql-compose\lib\InputTypeComposer.js:43:13)
    at Object.create (D:\Zied\work\weally\server\src\graphql/complaintAction.js:85:32)

but passing it createOneInputTC, createOneResolver or ComplaintActionTC both don't seem to correspond. I'm wondering why you don't call the static method InputTypeComposer.create inside the addFields() so you can be aware of the context

@ziedHamdi ziedHamdi reopened this Nov 5, 2019
@nodkz
Copy link
Member

nodkz commented Nov 5, 2019

InputTypeComposer.create and other type composer create type objects. They may use SDL, or just type names so they must know to which TypeRegistry (SchemaComposer instance they belong) for type lookup.

You may use schemaCompose.createITC() method instead, which already know what registry to use. Static method InputTypeComposer.create didn't know which registry to use, so it require to provide schemaComposer explicitly as the second argument.

When you reading types/resolvers createOneInputTC, createOneResolver or ComplaintActionTC – they know its type registry (schemaComposer) so there is no need provide registry explicitly.


BTW your code snippet a little bit strange. It can be done in such way:

createOneInputTC.addFields({
	complaintId: 'String',
        value: 'String',
 })

InputTypeComposer creates complex input OBJECT type.

@ziedHamdi
Copy link
Author

Ok Thanks Pavel.

The simple approach worked

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants