Resolvers should look for a custom resolver first, but this would break the mixed @client / server query
When resolving a field, the resolve function should first look for a custom resolver, thus simulating the same behavior as on a server :
const query = gql`
query {
messages {
content
user {
username
}
}
}
`;
const resolvers = {
Message: {
user: ({ user }) => ({
...db.getUserById(user),
__typename: 'User',
}),
},
Query: {
messages: () => [{ id: "m1", content: "message 1", user: "u1", __typename: "Message" }]
}
}
With apollo-link-state, due to this statement stating that if there is a field in the root value, it's because the server already sent something. It's not true, this statement should be valid only if we are at the Query level.
Here is a failing test :
it('runs resolvers for nested client queries', done => {
const nestedQuery = gql`
query NestedQuery {
foo @client {
bar
nestedBar {
baz
}
}
}
`;
const getNestedBarById = id => id === '42' ? {
baz: true,
} : null;
const client = withClientState({
resolvers: {
Foo: {
nestedBar: ({ nestedBar }) => getNestedBarById(nestedBar),
},
Query: {
foo: () => ({ bar: true, nestedBar: '42', __typename: 'Foo' }),
},
}
});
execute(client, { query: nestedQuery }).subscribe(({ data }) => {
expect(data).toEqual({ foo: { bar: true, nestedBar: { baz: true } } });
done();
}, done.fail);
});
// TypeError: Cannot read property 'baz' of undefined
Quick edit (to be refactored) to make all the tests pass (this one included) :
in index.js
const resolver = (fieldName, rootValue = {}, args, context, info) => {
//resultKey is where data under the field name is ultimately returned by the server
//https://github.com/apollographql/apollo-client/tree/master/packages/graphql-anywhere#resolver-info
const fieldValue = rootValue[info.resultKey];
//If fieldValue is defined and we are at the Query level, server returned a value
if (fieldValue !== undefined && (((rootValue as any).__typename || type) == 'Query')) {
return fieldValue;
}
// Look for the field in the custom resolver map
const resolverMap = resolvers[(rootValue as any).__typename || type];
if (resolverMap) {
const resolve = resolverMap[fieldName];
if (resolve) return resolve(rootValue, args, context, info);
if (fieldValue !== undefined) return fieldValue;
}
if (fieldValue !== undefined) {
return fieldValue;
}
//TODO: the proper thing to do here is throw an error saying to
//add `client.onResetStore(link.writeDefaults);`
//waiting on https://github.com/apollographql/apollo-client/pull/3010
//Currently with nested fields, this sort of return does not work
return defaults[fieldName];
};
Hi @PCreations, can you please send in a PR with the failing test and fix? It's impossible for me to see the file diff otherwise. Thanks!
Sure thing ! Here we go : https://github.com/apollographql/apollo-link-state/pull/235
any chance someone could explain the "this would break the mixed @client / server query" bit? My team is running into this issue and are looking to help with a fix.
Related: #272
Still running into this issue, anyone looking into this or pointers for a fix? Happy to take a stab at this myself.
@marktani I just tested on Apollo client 2.1 and this seems to be, for lack of a better word, resolved. If you can migrate then it will probably solve the problem, complete with async resolution:
Example client side schema
const typeDefs = gql`
extend type Query {
getTasks: [Task]
isLoggedIn: Boolean
}
type Task {
id: String
comments: [Comment]
}
type Comment {
id: String
commentText: String
}
`;
For example, defining this as the resolver:
const client = new ApolloClient({
cache,
resolvers: {
Query: {
getTasks: (root, variables, { cache, getCacheKey }) => {
return [
{
__typename: "Task",
id: "test"
}
];
}
},
// Help to resolve the dependencies
Task: {
comments: async () => {
return [
{
__typename: "Comment",
id: "commentId",
commentText: "the work text"
},
{
__typename: "Comment",
id: "commentId2",
commentText: "another comment"
}
];
}
}
},
typeDefs
});
const GET_TASKS = gql`
query {
getTasks @client {
id
comments {
id
commentText
}
}
}
`;
I am experiencing this issue currently when nesting more than one level. I will try and get around to a complete code example soon. For now I'm looking for workarounds.
Here is the quick and dirty:
This works, resolving both community and child posts data using a posts resolver within the Community resolver object :
query PostsQuery {
community(slug: "test") @client {
id
name
posts @client {
id
title
}
}
}
This query breaks complaining that there is no comments field found in the results despite that I do have a comments resolver on the Post resolver object:
query PostsWithCommentsQuery {
community(slug: "test") @client {
id
name
posts @client {
id
title
comments @client {
id
text
}
}
}
}