ardatan / ardatan/graphql-tools
Selection set for nested object with batchDelegateToSchema
- Dominant language
- TypeScript
- Stars
- 5.4k
- Forks
- 830
- Avg merge
- 10h 59m
- Merged PRs (30d)
- 45
Description
**Is your feature request related to a problem? Please describe.**
Unable (or do not know how) to specify a selectionSet on a subschema when using `batchDelegateToSchema` (not sure about `delegateToSchema` yet). In my specific situation, I need to implement a custom `valuesFromResults` which requires a specific field to be present on the `results`, regardless if the client requested that field or not. I will provide an example below.
**Describe the solution you'd like**
On the selection set string, I was expecting to be able to select sub-objects without having to build the entire `SelectionSetNode`. If I did need to build the node, I was unable to find a straightforward way of dynamically building that in combination with what the client is requesting.
**Describe alternatives you've considered**
I looked into implementing `selectionSet` on `batchDelegateToSchema`, but it seemed pretty burdensome to have to build the full `SelectionSetNode` object (combining what is always required with what the client requested). It is possible there is a simple way of doing this that I was unable to locate.
**Additional context**
Code Sample showcasing this. Querying `allPosts` without requesting `user.id` should replicate the issue.
```typescript
import { batchDelegateToSchema } from '@graphql-tools/batch-delegate';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { stitchSchemas } from '@graphql-tools/stitch';
import { OperationTypeNode } from 'graphql';
interface Post {
id: string;
text: string;
userId: string;
user: User;
}
interface User {
id: string;
email: string;
}
const postSchema = makeExecutableSchema({
typeDefs: /* GraphQL */ `
type Post {
id: ID!
text: String
userId: ID!
}
type Query {
allPosts: [Post]
}
`,
resolvers: {
Query: {
allPosts() {
return [
{
id: '1',
text: 'my post 1',
userId: 'userId1',
},
{
id: '2',
text: 'my post 2',
userId: 'userId2',
},
];
},
},
},
});
const userSchema = makeExecutableSchema({
typeDefs: /* GraphQL */ `
type User {
id: ID!
email: String
}
type Query {
usersByIds(ids: [ID!]!): [User]
}
`,
resolvers: {
Query: {
usersByIds(_, args) {
return args.ids
.map((id: string) => {
return {
id: id,
email: `${id}@email.com`,
};
})
.reverse(); // Reversing so a valuesFromResults is required to implement
},
},
},
});
// setup subschema config objects
const postsSubschema = { schema: postSchema };
const usersSubschema = { schema: userSchema };
export const experimentSchema = stitchSchemas({
subschemas: [postsSubschema, usersSubschema],
typeDefs: /* GraphQL */ `
extend type Post {
user: User!
}
`,
resolvers: {
Post: {
user: {
selectionSet: `{ userId }`, // Thought I could maybe write { userId user { id} } to force "id" to be requested
resolve(post: Post, args, context, info) {
return batchDelegateToSchema({
schema: usersSubschema,
operation: OperationTypeNode.QUERY,
fieldName: 'usersByIds',
key: post.userId,
argsFromKeys: (ids) => ({ ids }),
valuesFromResults(results: User[], keys) {
return keys.map((key) =>
results.find((result) => result.id === key) // "id" is required, regardless if client requests it
);
},
context,
info,
});
},
},
},
},
});
```
Contributor guide
Research direction
Start with batchDelegateToSchema and the Post.user resolver shown in the example; compare how selectionSet and valuesFromResults are handled. Determine how a nested selection-set string could combine required fields with the client selection. Done should ensure User.id is present for valuesFromResults while preserving the requested User fields and correctly mapping the reversed usersByIds results.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- graphql, typescript
- Domain
- api
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100