Apollo v3 Graphql concatPagination for a specific field of a query? - apollo-client

I am fairly newbie when it comes down to graphql. I have the following schema
type query {
allJobs (
limit: Int
cursorId: String
): JobSearchResults!
type JobSearchResults {
jobs: [Job!]
hasMoreJobs: Boolean!
}
}
So there is the query allJobs and the result is an object with jobs array and a simply boolean hasMoreJobs to signal the end of the jobs.
On the client side I am able to query this and get results, but I am totally confused on how to cache these results. On ApolloClient I have the following:
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
// cache the previous results and concat the new results to original data
allJobs: concatPagination(),
},
},
},
}),
I know that this would work if I was just returning an array of jobs like that.
type query {
allJobs (
limit: Int
cursorId: String
): [Jobs]!
My question is if there is a way to use concatPagination to cache only the jobs: [Job!] from the original query.
Or if there is a better way to deal with this problem? Maybe I need to rethink and reconstruct the original schema?

I think you'll find the solution of your problem here:
https://www.apollographql.com/blog/pagination-and-infinite-scrolling-in-apollo-client-59ff064aac61/

Related

KeystoneJS relationships, how can I connect using an array of ids

I am using the new version Keystone Next and I am trying to connect multiple items at once using an array of ids. It seems connect supports that, accepting an array of objects.
const FINISH_VOCABULARY_QUIZ_MUTATION = gql`
mutation FINISH_VOCABULARY_QUIZ_MUTATION(
$userId: ID!
$wordId: ID!
) {
updateUser(id: $userId, data: {
wrongAnswers: {
connect: [{id: "idblabla"}, {id: "idblabla2"}]
}
}) {
id
}
}`;
But what I just can't seem to figure out is how do I pass this array of ids as a variable to my mutation.
I understand that I would need to create a new type? The documentation is still unfinished, so there is nothing on that yet.
I have also tried using string interpolation to form my query, but it seems that it's not a thing in GraphQl.
This is more of a GraphQL question than a KeystoneJS but one but to head to the right direction here you'd need to change your query to something like below:
const FINISH_VOCABULARY_QUIZ_MUTATION = gql`
mutation FINISH_VOCABULARY_QUIZ_MUTATION(
$userId: ID!,
$ids: [UserWhereUniqueInput!]!
) {
updateUser(id: $userId, data: {
wrongAnswers: {
connect: $ids
}
}) {
id
}
}`;
And then map your array of ids to an array of objects with id fields.
There is a better method:
const FINISH_VOCABULARY_QUIZ_MUTATION = gql`
mutation FINISH_VOCABULARY_QUIZ_MUTATION(
$userId: ID!,
$data: SomeAPIDefinedMutationUniqueInput
) {
updateUser(id: $userId, data: $data)
id
}
}`;
This way you:
don't have to define types for internal arguments ($wordsIdWrong: [WordWhereUniqueInput]);
can reuse/share this mutation - import it from some, common for queries, place (dir) - just call it with different data variables;
easier for reading/maintenance;
PS. To be honest, there should be some specific [to quizes] mutation (don't use userUpdate for that), with user (or better quiz) id defined within SomeAPIDefinedUniqueInput.

React Relay paginate on a query instead of a fragment

I have been looking for a way to paginate on a standard relay graphql query, rather than creating a fragment and paginating on that. I havent really been able to find any documentation on how to achieve such a thing. I just want to run the query, come up with the first n records, and then run the query again (although i read thats not necessarily best practice, and then load the next 20 but increasing the count and running the query... has anyone done such a thing?
What I want in theory is...
const = useLazyLoadQuery{
data,
loadNext,
loadPrevious,
hasNext,
hasPrevious,
isLoadingNext,
isLoadingPrevious,
refetch, // For refetching connection
} = usePaginationFragment(
graphql`
fragment Table_user on User
#refetchable(queryName: "UserQuery")
#argumentDefinitions(
count: { type: "Int", defaultValue: 20 }
cursor: { type: "String" }
) {
query UserQuery(
$first: Int!,
$after: String
) {
users(search: $search) {
id
name
phone
email
postalCode
status
referralCode
products
updatedAt
nextAssignmentOn
}
}
}
I'm sure theres a way to do this, but many attempts have failed
usePaginationFragment does not do any fetch
you need to useLazyLoadQuery or usePreloadQuery to do the fetch
useFragment, usePaginationFragment only declares data requirements for fragments
check the new example here https://github.com/relayjs/relay-examples/pull/104

Sorting results in AWS Amplify GraphQL without filtering

Provided a very simple model in graphql.schema, how would I perform a simple sort query?
type Todo #model
id: ID!
text: String!
}
Which generates the following in queries.js.
export const listTodos = /* GraphQL */ `
query ListTodos(
$filter: ModelTodoFilterInput
$limit: Int
$nextToken: String
) {
listTodos(filter: $filter, limit: $limit, nextToken: $nextToken) {
items {
id
text
}
nextToken
}
}
`;
I have found multiple sources pointing me in the direction of the #key directive. This similar question addresses that approach (GraphQL with AWS Amplify - how to enable sorting on query).
While that may seem promising and successfully generates new queries I can use, all the approaches I have tried require that I filter the data before sorting it. All I want to do is sort my todo results on a given column name, with a given sort direction (ASC/DESC).
This is how I would perform a simple (unsorted) query:
const todos = await API.graphql(graphqlOperation(listTodos));
I would be looking to do something along the lines of:
const todos = await API.graphql(graphqlOperation(listTodos, {sortField: "text", sortDirection: "ASC"} )).
Decorate your model with the #searchable directive, like so:
type Todo #model #searchable
{
id: ID!
text: String!
}
After that, you can query your data with sorting capabilities like below:
import { searchToDos } from '../graphql/queries';
import { API, graphqlOperation } from 'aws-amplify';
const toDoData = await API.graphql(graphqlOperation(searchToDos, {
sort: {
direction: 'asc',
field: 'text'
}
}));
console.log(toDoData.data.searchToDos.items);
For more information, see
https://github.com/aws-amplify/amplify-cli/issues/1851#issuecomment-545245633
https://docs.amplify.aws/cli/graphql-transformer/directives#searchable
Declaring #searchable incurs pointless extra server cost if all you need is straight forward sorting. It spins up an EBS and an OpenSearch that will be about $20 a month minumum.
Instead you need to use the #index directive.
As per the documentation here: https://docs.amplify.aws/guides/api-graphql/query-with-sorting/q/platform/js/
In your model, add the #index directive to one of the fields with a few parameters:
type Todo #model {
id: ID!
title: String!
type: String! #index(name: "todosByDate", queryField: "todosByDate", sortKeyFields: ["createdAt"])
createdAt: String!
}
By declaring the queryField and the sortKeyField you will now have a new query available to once you push your amplify config:
query todosByDate {
todosByDate(
type: "Todo"
sortDirection: ASC
) {
items {
id
title
createdAt
}
}
}
The field you declare this directive on can not be empty (notice the ! after the field name)
This is a much better way of doing it as opposed to #searchable, which is massively overkill.
I've accepted MTran's answer because it feels to me it is the nearest thing to an actual solution, but I've also decided to actually opt for a workaround. This way, I avoid adding a dependency to ElasticSearch.
I ended up adding a field to my schema and every single entry has the same value for that field. That way, I can filter by that value and still have the entire table of values, that I can then sort against.

how to get the Graphql request body in apollo-server [duplicate]

I have written a GraphQL query which like the one below:
{
posts {
author {
comments
}
comments
}
}
I want to know how can I get the details about the requested child fields inside the posts resolver.
I want to do it to avoid nested calls of resolvers. I am using ApolloServer's DataSource API.
I can change the API server to get all the data at once.
I am using ApolloServer 2.0 and any other ways of avoiding nested calls are also welcome.
You'll need to parse the info object that's passed to the resolver as its fourth parameter. This is the type for the object:
type GraphQLResolveInfo = {
fieldName: string,
fieldNodes: Array<Field>,
returnType: GraphQLOutputType,
parentType: GraphQLCompositeType,
schema: GraphQLSchema,
fragments: { [fragmentName: string]: FragmentDefinition },
rootValue: any,
operation: OperationDefinition,
variableValues: { [variableName: string]: any },
}
You could transverse the AST of the field yourself, but you're probably better off using an existing library. I'd recommend graphql-parse-resolve-info. There's a number of other libraries out there, but graphql-parse-resolve-info is a pretty complete solution and is actually used under the hood by postgraphile. Example usage:
posts: (parent, args, context, info) => {
const parsedResolveInfo = parseResolveInfo(info)
console.log(parsedResolveInfo)
}
This will log an object along these lines:
{
alias: 'posts',
name: 'posts',
args: {},
fieldsByTypeName: {
Post: {
author: {
alias: 'author',
name: 'author',
args: {},
fieldsByTypeName: ...
}
comments: {
alias: 'comments',
name: 'comments',
args: {},
fieldsByTypeName: ...
}
}
}
}
You can walk through the resulting object and construct your SQL query (or set of API requests, or whatever) accordingly.
Here, are couple main points that you can use to optimize your queries for performance.
In your example there would be great help to use
https://github.com/facebook/dataloader. If you load comments in your
resolvers through data loader you will ensure that these are called
just once. This will reduce the number of calls to database
significantly as in your query is demonstrated N+1 problem.
I am not sure what exact information you need to obtain in posts
ahead of time, but if you know the post ids you can consider to do a
"look ahead" by passing already known ids into comments. This will
ensure that you do not need to wait for posts and you will avoid
graphql tree calls and you can do resolution of comments without
waiting for posts. This is great article for optimizing GraphQL
waterfall requests and might you give good idea how to optimize your
queries with data loader and do look ahead
https://blog.apollographql.com/optimizing-your-graphql-request-waterfalls-7c3f3360b051

How do I create a mutation that pushes to an array rather than replacing it?

I've been playing with GraphQL recently, and am currently learning about mutations. I'm a bit confused with something. I have a model Post with relation Comments. I have a mutation that looks like this:
mutation addCommentToPost {
updatePost(
id: "POST-1",
comments: [{
body: "Hello!"
}]
) {
id,
comments {
id,
body
}
}
}
The problem is, whenever I run this, it seems to remove all the comments and sets the comments to only the one I just added. To be more specific, how do I write a mutation that pushes to the comments array rather than replacing it?
You are using a mutation called updatePosts, which I assume (based on the name) simply updates a post by replacing the fields that are passed. If you want to use the updatePosts mutation to add a comment, you will first have to query for the post to get the current list of comments, add your comment to the end, and then call updateComment with the entire list of comments (including the one that you just added to the end).
However, this isn't really a good solution, especially if the list of comments is potentially very long. If you have the ability to change the GraphQL server, you should create a new mutation on the server with a signature like addComment(postId: ID, comment: CommentInput). In the resolve function for that mutation, simply add the comment that is passed to the end of the list of current comments.
// resolver for addComment:
addComment(root, args) {
// validate inputs here ...
const post = db.getPost(args.postId);
post.comments.append(args.comment);
db.writePost(post.id, post);
}
db.getPost and db.writePost are functions you have to define yourself to retrieve/write a post from/to wherever you store it.
It's important to note that unlike a SQL or Mongo query, a GraphQL mutation itself doesn't have any meaning without the resolve functions. What the mutation does is defined entirely inside its resolve function. Mutation names and arguments only gain meaning together with the resolve function. It's up to you (or the GraphQL server developers in your company) to write the resolve functions.
The way this situation is currently solved in the Graphcool API is to use a create mutation for the Comment that links to the Post. This is called a nested connect mutation.
This is how it would look like:
mutation {
createComment(
text: "Hello!"
postId: "POST-1"
) {
id
text
post {
comments {
id
}
}
}
}
In the future, other nested arguments like comments_set or comments_push could be introduced, then pushing would be possible like this:
mutation addCommentToPost {
updatePost(
id: "POST-1",
comments_push: [{
body: "Hello!"
}]
) {
id,
comments {
id,
body
}
}
}
Disclosure: I work at Graphcool.
You can use those code as an example for mutation.
module.exports = (refs) => ({
type: refs.commentType,
args: {
id: {
type: GraphQLString
},
body: {
type: GraphQLString
}
},
resolve: (parent, args, root) => {
return createUser(args);
}
});

Resources