Graphql-js: RFC: Extra property on field definition to pass extra metadata

Created on 20 Sep 2018  路  28Comments  路  Source: graphql/graphql-js

I'm currently adding extra properties to some graphql object field definitions, like the following:

const MutationType = new GraphQLObjectType({
  name: 'Mutation',
  fields: () => ({
    AddSomething: {
      // ... normal field properties
      somethingElse: {},
    }
  }),
})

And then using them later on via the info argument inside some middlewares (using graphql-middleware):

const mutationField = info.schema.getMutationType().getFields()[info.fieldName];
console.log(mutationField.somethingElse);

For more details, see the following medium post: graphql mutation arguments validation using yup


The thing is, this is relying on internal behavior.
The following code spreads all properties given to the field:
https://github.com/graphql/graphql-js/blob/81719749e01f030cfb3a01a97e7e4bfc534bb08f/src/type/definition.js#L720-L724

Is that something expected to not change? If yes, then no need for any other extra property or for this issue. 馃槃

But if this is something that can change in future versions, I would love the possibility of having an extra field for that extra metadata.

I'm available to work on adding this, if it's approved.

enhancement

Most helpful comment

It took longer than I planned but I finally merged it in #2097
Thanks for patience 馃檱
I will try to cut release today/tomorrow

All 28 comments

Is that something expected to not change?

Yes. It's explicitly forbidden in Flow typings:
https://github.com/graphql/graphql-js/blob/3789a877c9ca6757c2c69ec965ea7dfb87f741eb/src/type/definition.js#L810-L822

Note: {| ... |} construct in Flow it's called exact type and it explicitly forbids any other properties. Sadly TS doesn't support it: https://github.com/Microsoft/TypeScript/issues/12936

I would love the possibility of having an extra field for that extra metadata.

@JCMais Agree 馃憤 Plus it provides a clean solution to #1343 without changing directive semantics.

I'm all for it! 馃憤

When you want to modularize the typeDefs + resolvers definitions across your codebase, it's great to be able to pass additional metadata for consumption by third-party libraries such as graphql-middleware.

Why not add an extensions key, so it makes the parallel with the GraphQLError extension point.

+1 for extensions

Ideally libraries using this would allow to customize the key name, to reduce possibility of key collisions.

what can be done to move this forward?

I think the first part is to remove exact types on flow definitions

+1 this could help fix issues with join-monster that requires extra metadata for fields

I would treat this exact Flow definition as a bug because this quote from @leebyron (https://github.com/graphql/graphql-js/issues/1262#issuecomment-368110891):

You can include any arbitrary data on a GraphQLObjectType that you like - it's just a JS object after all.

So I treat this pattern as a recomended way to have some metadata on types/fields like directives allows use to do in SDL.

I would treat this exact Flow definition as a bug because this quote

This quote is about actual GraphQL*Type not about config types like GraphQL*TypeConfig.

You can include any arbitrary data on a GraphQLObjectType that you like - it's just a JS object after all.

It basically says you can do:

const someType = new GraphQLObjectType({
  name: 'Foo',
  // ...
});

someType.customProperty = "Custom Value";

Personally, I think it's a workaround especially for clients who use Flow/TS at the same time I think config types should remain exact.


The alternative proposal would be:

const someType = new GraphQLObjectType({
  name: 'Foo',
  // ...
  extensions: {
    customProperty: "Custom Value",
  },
});

Where extensions defined as ObjMap<mixed>.

@JCMais What do you think about this proposal?

@IvanGoncharov that was exactly the kind of alternative I was looking for.

Just a specific key that we can be sure it will be available later on, from the info argument inside resolvers, for example.

This quote is about actual GraphQLType not about config types like GraphQLTypeConfig.

someType.customProperty = "Custom Value";

So how to place metadata on fields and args like directives do by using this way?
We configure fields and its args in a type config for GraphQLObjectType as a plain objects.

Should we mutate this object later (by using (schema.getType("typeName") as GraphQLObjectType).getFields()["fieldName"].complexity = 5;) or is it safe to rely on ...fieldConfig behavior in that case?

Should we mutate this object later (by using (schema.getType("typeName") as GraphQLObjectType).getFields()["fieldName"].complexity = 5;) or is it safe to rely on ...fieldConfig behavior in that case?

@19majkel94 It should work fine as a workaround until we ship an extensions solution however no long-term guarantees afterward.

I will work on adding immutable extensions into types, fields, args and enum values on next week so we will be able to ship it in 14.2.0.

馃洃Before shipping this we should consider how extensions can be provided via SDL.

Add @extensions directive like so:

type User {
  name @extensions(cost: { complexity: 2 })
}

We MUST provide a unified solution for SDL and configObjects. And will be nice if it will be covered by the static analysis (if possible of course). For now mixed value for extensions is weak and error-prone.

Via SDL it can be extended somehow via directive config. With Flowtype/Typescript it can be validated via explicitly provided type for extensions config object.

@nodkz this sounds good, since it has been some time since I last used graphql-js directly, how is the custom directive going to be extended by library authors?

That would be like a schema directive that apollo has, correct? Do we have support for that currently?

Last time I checked (which has been some time ago) custom directives were one of the most dark features from an user pov, when using graphql-js.

Currently, directives cannot be extended. There was no purpose. But for unified @extensions solution it may be required option, maybe not. Maybe somebody suggests a more elegant way.

We've been using it very effectively at Gatsby through graphql-compose extensions. Adding it here as an example of "usage in the wild".

@IvanGoncharov any updates on this? I'm working on implementing apollo-federation support into type-graphql, this would be a clean way of supporting everything needed to piece it together cleanly.

@j Thanks for pinging 馃憤
It looks like we have consensus around extensions feature so I will try to prepare PR this week.

@IvanGoncharov yay :) I can start the process of implementing in both libraries mentioned above.

Is extensions going to simply be part of the *Type config as { [key: string]: any}.

And if the PR lands this week, is there an ETA on a release? Simply because we've sort of put a freeze on our back-end until federation is supported intype-graphql. :)

Also, can/will extensions be added to the type.toConfig()?

Is extensions going to simply be part of the *Type config as { [key: string]: any}.

Yes, except it would be unknown (mixed in Flow) instead of any. Also, it would be { [key: string]: unknown} in config (and toConfig result) but inside object itself it would be { readonly [key: string]: mixed }

And if the PR lands this week, is there an ETA on a release? Simply because we've sort of put a freeze on our back-end until federation is supported intype-graphql. :)

I will make 14.5.0 after this merge.

Also, can/will extensions be added to the type.toConfig()?

Definitely, also I need to support it in other places, e.g. lexicographicSortSchema should sort extensions.

Maybe string and symbol for keys? This can ensure we don't have collisions

@cliedeman We can definitely use Symbols for solving collisions inside in-memory schema but it will just push the problem down the road. For example, I have a long term plan to figure out how to allow exposing extensions through introspection but it's impossible to use symbols inside introspection responses.

So my proposal is to keep everything simple for the first release and figure out how to solve problems only if they will happen in the wild.

BTW, you can also have collisions of directive names so it's not a new problem in GraphQL world.

@IvanGoncharov This push will make my week. :)

@j Just to give some transparency about the timeline.
At the moment I'm working on #2037 since it's security-related and this issue is second in my pipeline.
After both PRs merged I will release 14.5.0.

Quick update: #2037 taking more time than I initially expected so now I'm pushing for releasing at the end of this month. Thanks for the patience.

I see movement! Yay! :p

It took longer than I planned but I finally merged it in #2097
Thanks for patience 馃檱
I will try to cut release today/tomorrow

Thank you @IvanGoncharov !

@IvanGoncharov holy moly, the GraphQL world is going to get so much better now. :) Thanks!

Was this page helpful?
0 / 5 - 0 ratings