Apollo declare cache hit metrics without cacheControl - caching

We implemented a manual redis caching strategy for a query and removed the #cacheControl on it.
How do we declare that a cache is hit.
Example:
// old way had #cacheControl for 60 seconds
extend type Query {
comments(id: ID!): [Comment!]! #cacheControl(maxAge: 60)
}
// New: #cacheControl is removed
extend type Query {
comments(id: ID!): [Comment!]!
}
Query: {
comments: async (root, { id }, { injector, user }) => {
// getComments has redis caching
return await getComments(injector, id, user);
},
}

Related

Apollo Client 3: how to cache a mutation result into a nested collection? (collection within a collection)

In my Apollo Client 3 app, I am doing a mutation and want to cache the result into a collection which is nested within an item of a collection.
Specifically, I am creating a comment within a list of comments, each list within a post, each post within a list of posts. My app's data hierarchy looks like:
user 1
profile 1
post 1
comment 1.1
comment 1.2
post 2
comment 2.1
comment 2.2
< write mutation result here >
post 3
comment 3.1
comment 3.2
comment 3.3
...
In this situation, how would I best cache a created comment into its parent post's comment-collection? I am looking at the useMutation hook's update or modify config, but am not too sure.
For additional context, here is query that corresponds to the above data hierarchy:
query getUserPosts($userParams: GetUserParams!$postsPaginationParams: CursorPaginationParams!) {
user(params: $userParams) {
id
profile {
id
# ...
ownedPosts(pagination: $postsPaginationParams) {
items {
id
# ...
featuredComments {
id
primaryText
creationTimestamp
owner {
id
name
}
}
}
pagination {
# ...
}
}
}
}
}
And here is my mutation:
input CreateCommentParams {
ownerId: String!
postId: String!
primaryText: String!
}
mutation createComment($params: CreateCommentParams!) {
createComment(params: $params) {
id
owner {
id
name
}
primaryText
creationTimestamp
}
}
And here is what the useMutation is so far:
useMutation(CREATE_COMMENT_MUTATION, {
// ...
update: (cache, { data }) => {
if (data) {
const cacheId = cache.identify(data.createComment);
cache.modify({
fields: {
// ...how to update the comments array of the specific post?
}
})
}
},
})
You need to find the Post you are updating and update its featuredComments field like so:
useMutation(CREATE_COMMENT_MUTATION, {
// ...
update: (cache, { data }) => {
cache.modify({
id: cache.identify({
__typename: 'Post', // Assuming your this is the _typename of your Post type in your schema
id: postId,
}),
fields: {
featuredComments: (previous, { toReference }) => [...previous, toReference(data.createComment)]
},
}),
}),
})

Limit Graphql results that belongs to owner in Strapi

I am using a simple Strapi policy like below to limit the REST result that belongs to the owner only, which documented in the following link.
https://github.com/strapi/strapi/issues/624
module.exports = async (ctx, next) => {
const { id, role } = ctx.state.user;
if(role !== 'administrator'){
ctx.query.owner = id;
}
await next();
};
Now I want to do the same for Graphql results, but it doesn't seems to work with the same code because "ctx.query" is undefined. I have tried looking at the all the request API but none of them seems to be work for Graphql query. The URL ended like 'http://localhost:1337/graphql', and 'ctx.request.query' is an empty [].
https://strapi.io/documentation/3.0.0-beta.x/guides/requests.html#api-reference
Here what I did to solve that problem:
Override the GraphQl Schema in api/activity/config/schema.graphql
module.exports = {
definition: ``,
query: `
notifications(sort: String, limit: Int, start: Int, where: JSON): [Activity]
`,
type: {},
resolver: {
Query: {
notifications: {
description: 'Return the auth user notifications',
policies: ['plugins.users-permissions.permissions'],
resolver: 'Activity.findNotifications'
},
},
},
};
Create a new function resolver in api/activity/controllers/Activity.js
module.exports = {
findNotifications(ctx) {
ctx.query = { ...ctx.query, owner: ctx.state.user.id }
if (ctx.query._q) {
return strapi.services.activity.search(ctx.query);
}
return strapi.services.activity.find(ctx.query);
},
}
In the controller you got the query and add the owner id filter.
Hope it helps.
Why are you looking at REST docs while searching for graphQL problem? There is no url query parsing at all.
Owner role (permissions) can be checked using policies - it's described here.
The following sections contain examples of misc permission/customization - resolvers have a context parameter. User data should (did't checked) be available at context.state.user.

Enumerating all fields from a GraphQL query

Given a GraphQL schema and resolvers for Apollo Server, and a GraphQL query, is there a way to create a collection of all requested fields (in an Object or a Map) in the resolver function?
For a simple query, it's easy to recreate this collection from the info argument of the resolver.
Given a schema:
type User {
id: Int!
username: String!
roles: [Role!]!
}
type Role {
id: Int!
name: String!
description: String
}
schema {
query: Query
}
type Query {
getUser(id: Int!): User!
}
and a resolver:
Query: {
getUser: (root, args, context, info) => {
console.log(infoParser(info))
return db.Users.findOne({ id: args.id })
}
}
with a simple recursive infoParser function like this:
function infoParser (info) {
const fields = {}
info.fieldNodes.forEach(node => {
parseSelectionSet(node.selectionSet.selections, fields)
})
return fields
}
function parseSelectionSet (selections, fields) {
selections.forEach(selection => {
const name = selection.name.value
fields[name] = selection.selectionSet
? parseSelectionSet(selection.selectionSet.selections, {})
: true
})
return fields
}
The following query results in this log:
{
getUser(id: 1) {
id
username
roles {
name
}
}
}
=> { id: true, username: true, roles: { name: true } }
Things get pretty ugly pretty soon, for example when you use fragments in the query:
fragment UserInfo on User {
id
username
roles {
name
}
}
{
getUser(id: 1) {
...UserInfo
username
roles {
description
}
}
}
GraphQL engine correctly ignores duplicates, (deeply) merges etc. queried fields on execution, but it is not reflected in the info argument. When you add unions and inline fragments it just gets hairier.
Is there a way to construct a collection of all fields requested in a query, taking in account advanced querying capabilities of GraphQL?
Info about the info argument can be found on the Apollo docs site and in the graphql-js Github repo.
I know it has been a while but in case anyone ends up here, there is an npm package called graphql-list-fields by Jake Pusareti that does this. It handles fragments and skip and include directives.
you can also check the code here.

How to update apollo cache after mutation (query with filter)

I am pretty new to GraphQL. I am using graph.cool in a Vue.js project with apollo.
I am using right now the in-memory cache.
I had previously a simple 'allPosts' query.
And after creating a new one, I used the update() hook and readQuery() + writeQuery()
However I want that logged in users can only see their posts. So I modified the query with a filter.
query userStreams ($ownerId: ID!) {
allStreams(filter: {
owner: {
id: $ownerId
}
}) {
id
name
url
progress
duration
watched
owner {
id
}
}
}
My thought was, that I only need to pass in the userid variable. However this is not working. I am always getting
Error: Can't find field allStreams({"filter":{"owner":{}}}) on object (ROOT_QUERY) undefined.
this.$apollo.mutate({
mutation: CREATE_STREAM,
variables: {
name,
url,
ownerId
},
update: (store, { data: { createStream } }) => {
const data = store.readQuery({
query: USERSTREAMS,
variables: {
id: ownerId
}
})
data.allStreams.push(createStream)
store.writeQuery({
query: USER_STREAMS,
variables: {
id: ownerId
},
data
})
}
})
When you use readQuery or writeQuery, you should use the same variable name. So replace
variables: { id: ownerId }
With
variables: { ownerId }
Also, the reason you are getting an exception is that readQuery throws an exception if the data is not in the store. That happens before the first time you use writeQuery (or get the data with some other query).
You could write some default values to the store before calling this mutation.
You could also use readFragment that returns null instead of throwing an exception. But that would require more changes to your code.

Can an Apollo Mutation return a custom result object?

The result returned from an Apollo mutation is typically a type or a subset of fields from a type, and this is usually great. So my mutation:
const addUserMutation = gql`
mutation createUser($email: String!, $permissions: [CreatePermissionInput]) {
createUser(input: {email: $email, permissions: $permissions}) {
id
created
lastUpdated
uid
email
permissions {
who
...
}
}
}
`
Which is calling:
extend type Mutation {
createUser(input: CreateUserInput!): User
}
Will return the user with the fields listed.
Problem is, I want to know if the user that we just tried to create already existed or not. So how can I edit the response to include this flag? Can you have a mutation return, say, an object like:
{
exists: true,
user: { ... }
}
So I can do this:
this.props.submit({
variables: {
email,
permissions,
},
}).then(({ result }) => {
console.log(result)
// > { exists: true, user: [USER OBJECT] }
})
I get that this will break the auto cache update but sometimes you need the response from an update to tell you more.
Create an additional type for the return result of mutation
type UserPayLoad {
exists:Boolean
user:User
}
extend type Mutation {
createUser(input: CreateUserInput!): UserPayLoad
}
Just try this. This may help you

Resources