GraphQL SDL enum types - enums

We have a bunch of enum types defined in an SDL and they work great for queries and mutations.
In the resolvers section these are mapped to the strings which represent those enums in the back end.
For example in the SDL we have :
enum WRRole {
USER
PROVIDER
SUPPORT
ADMIN
SUPER_ADMIN
GUEST
}
And in the resolvers section we have :
WRRole: {
USER: 'user',
PROVIDER: 'provider',
SUPPORT: 'support',
ADMIN: 'admin',
SUPER_ADMIN: 'super admin',
GUEST: 'guest'
},
The resolvers match the enum values defined in the nodejs backend using Mongoose where the field is defined as :
...
roles: {
type: [
{
type: String,
enum: ['user', 'provider', 'support', 'admin', 'super admin', 'guest']
}
],
default: ['user']
},
...
The issue we have with GraphQL enums is that we cannot introspect the enums and get back the mappings using GraphQL introspection....
This causes issues with constructing UI's where we want to present the user with a dropdown list of these as options. The SDL enum values such as SUPER_USER are great for the keys but we want to display the actual backend mapped value to the use to select from.
This is just one example of many enums we have. Many of the mapped values consist of multiple words that have spaces between or words containing characters not allowed in the SDL enum value such as "super admin" in this case.
So my question is... How are you all handling stuff like this without having to repeat yourself or add more code to the front end to map these to more useful meaningful names for presentation ????
The order cannot be guaranteed to match the defined order of the enums in the backend so adding to the backend model enums would seriously mess up any assumptions that SUPER_USER actually maps to "super user" even though the resolver knows this.
Regards
Steve

While there isn't a perfect solution for this IMHO
there are a couple of ways I would approach this problem
Elegant way
You can add to your graphql schema a query that receives the Enum name
like so (in SDL ) :
type Query {
getEnumValues(enumName: String!): [EnumKeyValue!]!
}
type EnumKeyValue {
key: String!
value: String
}
this requires you to change your backend code a bit, for instance I would change the Enum resolver to get it's data from an object like so:
const enums = {
WRRole: {
USER: 'user',
PROVIDER: 'provider',
...
}
};
const enumResolver = {
WRRole: {
USER: enums.WRRole.USER,
PROVIDER: enums.WRRole.PROVIDER,
...
}
};
and then the resolver for getEnumValues would look like:
const queryResolvers = {
getEnumValues(source, args) {
const enumKey = args.enumName;
// enums is the same enums object from the previous example
return Object.keys(enums[enumKey]).map(key => ({
key,
value: enums[enumKey][key]
}))
}
};
Dirty, Abusive but quick
Another possible way, that is a bit abusive is to add a description on the Enum value
so your Enum SDL would like:
enum WRRole {
# user
USER
# provider
PROVIDER
# support
SUPPORT
# admin
ADMIN
# super admin
SUPER_ADMIN
# guest
GUEST
}
And then you can get the mapping between the key and the description with the following query:
{
__type(name: "WRRole") {
enumValues {
description
name
}
}
}

Related

Apollo Client 3: How to implement caching on client side for graphql interfaces?

I have a case where I have an interface, which has different type implementations defined in graphql. I may not be able to share the exact code. But the case looks something like:
interface Character {
name: String!
}
type Human implements Character {
name: String!
friends: [Character]
}
type Droid implements Character {
name: String!
material: String
}
There is query which returns either Human or Droid type in response.
Response may contain something like:
{
name: 'Human_01',
friends: []
__typename: 'Human'
}
or
{
name: 'Droid_01',
material: 'Aluminium'
__typename: 'Droid'
}
I am using Apollo Client 3 on client side for querying the data and have fragments for these like:
fragment Human on Human {
friends
}
fragment Droid on Droid {
material
}
fragment Character on Character {
name
...Human
...Droid
}
I am querying for the Character data as:
character {
...Character
}
Since, this is the case of interface, and as defined in the docs for Apollo client 3, we need to use possibleTypes in order to match the fragments in such cases. For caching purpose, I have defined InMemoryCache as:
new InMemoryCache({ possibleTypes: { Character: ['Human', 'Droid'] } })
The primary key field for a Character implementation is the name field, which I need to use in order to store its value in cache.
In Apollo client 3, it is mentioned to use typePolicies for defining keyFields for a type.
So, I need to ask as to whether I should define, type policy for both type implementations, specifying keyFields as name in both cases like:
new InMemoryCache({
possibleTypes: { Character: ['Human', 'Droid'] },
typePolicies: { Human: { keyFields: ['name'] }, Droid: { keyFields: ['name'] } }
});
In my example, I have provided only 2 such type implementations but there can be n number of type implementations corresponding to Character interface. So, in that case I will need to define keyFields as name in typePolicies for all the n type implementations.
So, does there exist any better way of implementing caching wrt these types of interface implementations ?
Any help would really be appreciated. Thanks!!!
Inheritance of type and field policies is coming in the next minor version of #apollo/client, v3.3!
You can try it out now by installing #apollo/client#3.3.0-beta.5.
To stay up to date on the progress of the v3.3 release, see this pull request.

I dont want to redefine properties when defining a GraphQL type. Is there a way to get past it? [duplicate]

Is it possible to use inheritance with GraphQL input types?
Something like that (this, of course, doesn't work with input types):
interface UserInputInterface {
firstName: String
lastName: String
}
input UserInput implements UserInputInterface {
password: String!
}
input UserChangesInput implements UserInputInterface {
id: ID!
password: String
}
No, the spec does not allow input types to implement interfaces. And GraphQL type system in general does not define any form of inheritance (the extends keyword adds fields to an existing type, and isn't for inheritance). The spec is intentionally constrained to stay simple. This means that you're stuck repeating fields across input types.
That said, depending on the way you construct your schema, you could build some kind of type transformer that appends the common fields programmatically based on some meta-data, e.g. a directive.
Better yet, you might be able to solve your problem via composition (always keep composition over inheritance in mind).
E.g.
input Name {
firstName: String
lastName: String
}
input UserInput {
name: Name
password: String!
}
input UserChangesInput {
name: Name
id: ID!
password: String
}
The client now has to send an object a level deeper, but that doesn't sound like much of a price for avoiding big repeating chunks. It might actually be good for the client as well, as they can now have common logic for building names, regardless of the query/mutation using them.
In this example, where it's only 2 simple fields, this approach is an overkill, but in general - I'd say it's the way to go.
Starting with the June2018 stable version of the GraphQL spec, an Input Object type can extend another Input Object type:
Input object type extensions are used to represent an input object type which has been extended from some original input object type.
This isn't inheritance per se; you can only extend the base type, not create new types based on it:
extend input MyInput {
NewField: String
}
Note there is no name for the new type; the existing MyInput type is extended.
The JavaScript reference implementation has implemented Input Object extensions in GraphQL.js v14 (June 2018), though it's unclear how to actually pass the extended input fields to a query without getting an error.
For actual type inheritance, see the graphql-s2s library.
It's doable using a custom directive.
Code Summary
const typeDefs = gql`
directive #inherits(type: String!) on OBJECT
type Car {
manufacturer: String
color: String
}
type Tesla #inherits(type: "Car") {
manufacturer: String
papa: String
model: String
}
type Query {
tesla: Tesla
}
`;
const resolvers = {
Query: {
tesla: () => ({ model: 'S' }),
},
Car: {
manufacturer: () => 'Ford',
color: () => 'Orange',
},
Tesla: {
manufacturer: () => 'Tesla, Inc',
papa: () => 'Elon',
},
};
class InheritsDirective extends SchemaDirectiveVisitor {
visitObject(type) {
const fields = type.getFields();
const baseType = this.schema.getTypeMap()[this.args.type];
Object.entries(baseType.getFields()).forEach(([name, field]) => {
if (fields[name] === undefined) {
fields[name] = { ...field };
}
});
}
}
const schemaDirectives = {
inherits: InheritsDirective,
};
Query:
query {
tesla {
manufacturer
papa
color
model
}
}
Output:
{
"data": {
"tesla": {
"manufacturer": "Tesla, Inc",
"papa": "Elon",
"color": "Orange",
"model": "S",
}
}
}
Working example at https://github.com/jeanbmar/graphql-inherits.
If you came here looking for an explanation for the "implements", keyword, here it is:
An object type must be a super‐set of all interfaces it implements. The object type must include a field of the same name for every field defined in an interface.
(Excerpt taken from the June 2018 GraphQL spec.)
Here's an example
interface Foo {
id: ID!
foo: Int!
}
type Bar implements Foo #entity {
id: ID!;
foo: Int!;
bar: Int!;
}
So the Bar type doesn't inherit from the Foo interface, but it implements it. The former must include all the fields that are listed in the latter.
I think that this is a nice way to annotate types that should be like other types.

GraphQL: how to have it return a flexible, dynamic array, depending on what the marketeer filled in? [duplicate]

We are in the situation that the response of our GraphQL Query has to return some dynamic properties of an object. In our case we are not able to predefine all possible properties - so it has to be dynamic.
As we think there are two options to solve it.
const MyType = new GraphQLObjectType({
name: 'SomeType',
fields: {
name: {
type: GraphQLString,
},
elements: {
/*
THIS is our special field which needs to return a dynamic object
*/
},
// ...
},
});
As you can see in the example code is element the property which has to return an object. A response when resolve this could be:
{
name: 'some name',
elements: {
an_unkonwn_key: {
some_nested_field: {
some_other: true,
},
},
another_unknown_prop: 'foo',
},
}
1) Return a "Any-Object"
We could just return any object - so GraphQL do not need to know which fields the Object has. When we tell GraphQL that the field is the type GraphQlObjectType it needs to define fields. Because of this it seems not to be possible to tell GraphQL that someone is just an Object.
Fo this we have changed it like this:
elements: {
type: new GraphQLObjectType({ name: 'elements' });
},
2) We could define dynamic field properties because its in an function
When we define fields as an function we could define our object dynamically. But the field function would need some information (in our case information which would be passed to elements) and we would need to access them to build the field object.
Example:
const MyType = new GraphQLObjectType({
name: 'SomeType',
fields: {
name: {
type: GraphQLString,
},
elements: {
type: new GraphQLObjectType({
name: 'elements',
fields: (argsFromElements) => {
// here we can now access keys from "args"
const fields = {};
argsFromElements.keys.forEach((key) => {
// some logic here ..
fields[someGeneratedProperty] = someGeneratedGraphQLType;
});
return fields;
},
}),
args: {
keys: {
type: new GraphQLList(GraphQLString),
},
},
},
// ...
},
});
This could work but the question would be if there is a way to pass the args and/or resolve object to the fields.
Question
So our question is now: Which way would be recommended in our case in GraphQL and is solution 1 or 2 possible ? Maybe there is another solution ?
Edit
Solution 1 would work when using the ScalarType. Example:
type: new GraphQLScalarType({
name: 'elements',
serialize(value) {
return value;
},
}),
I am not sure if this is a recommended way to solve our situation.
Neither option is really viable:
GraphQL is strongly typed. GraphQL.js doesn't support some kind of any field, and all types defined in your schema must have fields defined. If you look in the docs, fields is a required -- if you try to leave it out, you'll hit an error.
Args are used to resolve queries on a per-request basis. There's no way you can pass them back to your schema. You schema is supposed to be static.
As you suggest, it's possible to accomplish what you're trying to do by rolling your own customer Scalar. I think a simpler solution would be to just use JSON -- you can import a custom scalar for it like this one. Then just have your elements field resolve to a JSON object or array containing the dynamic fields. You could also manipulate the JSON object inside the resolver based on arguments if necessary (if you wanted to limit the fields returned to a subset as defined in the args, for example).
Word of warning: The issue with utilizing JSON, or any custom scalar that includes nested data, is that you're limiting the client's flexibility in requesting what it actually needs. It also results in less helpful errors on the client side -- I'd much rather be told that the field I requested doesn't exist or returned null when I make the request than to find out later down the line the JSON blob I got didn't include a field I expected it to.
One more possible solution could be to declare any such dynamic object as a string. And then pass a stringified version of the object as value to that object from your resolver functions. And then eventually you can parse that string to JSON again to make it again an object on the client side.
I'm not sure if its recommended way or not but I tried to make it work with this approach and it did work smoothly, so I'm sharing it here.

How to create generics with the schema language?

Using facebook's reference library, I found a way to hack generic types like this:
type PagedResource<Query, Item> = (pagedQuery: PagedQuery<Query>) => PagedResponse<Item>
​
interface PagedQuery<Query> {
query: Query;
take: number;
skip: number;
}
​
interface PagedResponse<Item> {
items: Array<Item>;
total: number;
}
function pagedResource({type, resolve, args}) {
return {
type: pagedType(type),
args: Object.assign(args, {
page: { type: new GraphQLNonNull(pageQueryType()) }
}),
resolve
};
function pageQueryType() {
return new GraphQLInputObjectType({
name: 'PageQuery',
fields: {
skip: { type: new GraphQLNonNull(GraphQLInt) },
take: { type: new GraphQLNonNull(GraphQLInt) }
}
});
}
function pagedType(type) {
return new GraphQLObjectType({
name: 'Paged' + type.toString(),
fields: {
items: { type: new GraphQLNonNull(new GraphQLList(type)) },
total: { type: new GraphQLNonNull(GraphQLInt) }
}
});
}
}
But I like how with Apollo Server I can declaratively create the schema. So question is, how do you guys go about creating generic-like types with the schema language?
You can create an interface or union to achieve a similar result. I think this article does a good job explaining how to implement interfaces and unions correctly. Your schema would look something like this:
type Query {
pagedQuery(page: PageInput!): PagedResult
}
input PageInput {
skip: Int!
take: Int!
}
type PagedResult {
items: [Pageable!]!
total: Int
}
# Regular type definitions for Bar, Foo, Baz types...
union Pageable = Bar | Foo | Baz
You also need to define a resolveType method for the union. With graphql-tools, this is done through the resolvers:
const resolvers = {
Query: { ... },
Pageable {
__resolveType: (obj) => {
// resolve logic here, needs to return a string specifying type
// i.e. if (obj.__typename == 'Foo') return 'Foo'
}
}
}
__resolveType takes the business object being resolved as its first argument (typically your raw DB result that you give GraphQL to resolve). You need to apply some logic here to figure out of all the different Pageable types, which one we're handling. With most ORMs, you can just add some kind of typename field to the model instance you're working with and just have resolveType return that.
Edit: As you pointed out, the downside to this approach is that the returned type in items is no longer transparent to the client -- the client would have to know what type is being returned and specify the fields for items within an inline fragment like ... on Foo. Of course, your clients will still have to have some idea about what type is being returned, otherwise they won't know what fields to request.
I imagine creating generics the way you want is impossible when generating a schema declaratively. To get your schema to work the same way it currently does, you would have to bite the bullet and define PagedFoo when you define Foo, define PagedBar when you define Bar and so on.
The only other alternative I can think of is to combine the two approaches. Create your "base" schema programatically. You would only need to define the paginated queries under the Root Query using your pagedResource function. You can then use printSchema from graphql/utilities to convert it to a String that can be concatenated with the rest of your type definitions. Within your type definitions, you can use the extend keyword to build on any of the types already declared in the base schema, like this:
extend Query {
nonPaginatedQuery: Result
}
If you go this route, you can skip passing a resolve function to pagedResource, or defining any resolvers on your programatically-defined types, and just utilize the resolvers object you normally pass to buildExecutableSchema.

Share structure between GraphQL schemas

I have a Apollo GraphQL server talking to an API returning responses with roughly the following structure:
{
"pagination": {
"page": 1,
// more stuff
},
sorting: {
// even more stuff
},
data: [ // Actual data ]
}
This structure is going to be shared across pretty much all responses from this API, that I'm using extensively. data is going to be an array most of the time, but can also be an object.
How can I write this in an efficient way, so that I don't have to repeat all these pagination and sorting fields on every data type in my schemas?
Thanks a lot!
I've sorted your problem by creating a lib called graphql-s2s. It enhances your schema by adding support for type inheritance, generic types and metadata. In your case, creating a generic type for your Paginated object could be a viable solution. Here is an example:
const { transpileSchema } = require('graphql-s2s')
const { makeExecutableSchema } = require('graphql-tools')
const schema = `
type Paged<T> {
data: [T]
cursor: ID
}
type Node {
id: ID!
creationDate: String
}
type Person inherits Node {
firstname: String!
middlename: String
lastname: String!
age: Int!
gender: String
}
type Teacher inherits Person {
title: String!
}
type Student inherits Person {
nickname: String!
questions: Paged<Question>
}
type Question inherits Node {
name: String!
text: String!
}
type Query {
students: Paged<Student>
teachers: Paged<Teacher>
}
`
const executableSchema = makeExecutableSchema({
typeDefs: [transpileSchema(schema)],
resolvers: resolver
})
I've written more details about this here (in Part II).
When you define your schema, you will end up abstracting out pagination, sorting, etc. as separate types. So the schema will look something like:
type Bar {
pagination: Pagination
sorting: SortingOptions
data: BarData # I'm an object
}
type Foo {
pagination: Pagination
sorting: SortingOptions
data: [FooData] # I'm an array
}
# more types similar to above
type Pagination {
page: Int
# more fields
}
type SortingOptions {
# more fields
}
type BarData {
# more fields
}
So you won't have to list each field within Pagination multiple times regardless. Each type that uses Pagination, however, will still need to specify it as a field -- there's no escaping that requirement.
Alternatively, you could set up a single Type to use for all your objects. In this case, the data field would be an Interface (Data), with FooData, BarData, etc. each implementing it. In your resolver for Data, you would define a __resolveType function to determine which kind of Data to return. You can pass in a typename variable with your query and then use that variable in the __resolveType function to return the correct type.
You can see a good example of Interface in action in the Apollo docs.
The downside to this latter approach is that you have to return either a single Data object or an Array of them -- you can't mix and match -- so you would probably have to change the structure of the returned object to make it work.

Resources