Catch the highlights of GraphQLConf 2023! Click for recordings. Or check out our recap blog post.
Docs
Plugins
Live Queries

Live Queries

GraphQL Live Query implementation from Laurin Quast can be used in GraphQL Mesh with a few additions in the configuration.

npm i @graphql-mesh/plugin-live-query

Basic Usage

You have a Query root field that returns all Todo entities from your data source like below.

query getTodos {
  todos {
    id
    content
  }
}

And you want to update this operation result automatically without manual refresh when Mutation.addTodo is called.

You only need to add the following to your existing configuration.

.meshrc.yaml
additionalTypeDefs: |
  directive @live on QUERY
plugins:
  - liveQuery:
      invalidations:
        - field: Mutation.addTodo
          invalidate:
            - Query.todos

Then you can send a live query with @live directive.

query getTodos @live {
  todos {
    id
    content
  }
}

This will start a real-time connection between the server and your client. The response of todos will get updated whenever addTodo is called.

ID Based Invalidation

Let’s say you have the following query that returns a specific Todo entity based on id field;

query getTodo($id: ID!) {
  todo(id: $id) {
    id
    content
  }
}

If you update this entity with editTodo mutation field on your backend, then you want to invalidate this entity specifically instead of validating all todo queries;

.meshrc.yaml
invalidations:
  - field: Mutation.editTodo
    invalidate:
      - Todo:{args.id}

In a case where the field resolver resolves null but might resolve to an object type later, e.g., because the visibility got update the field that uses a specific id argument can be invalidated in the following way:

.meshrc.yaml
invalidations:
  - field: Mutation.editTodo
    invalidate:
      - Query.todo(id:"{args.id}")

Programmatic Usage

liveQueryStore is available in GraphQL Context, so you can access it in resolvers composition functions that wrap existing resolvers or additional resolvers;

See Resolvers Composition

.meshrc.yaml
transforms:
  - resolversComposition:
      - resolver: Mutation.editTodo
        composer: invalidate-todo#invalidateTodo

And in this code file;

invalidate-todo.ts
module.exports = {
  invalidateTodo: next => async (root, args, context, info) => {
    const result = await next(root, args, context, info)
    context.liveQueryStore.invalidate(`Todo:${args.id}`)
    return result
  }
}

Config API Reference

  • ttl (type: Float) - Maximum age in ms. Defaults to Infinity. Set it to 0 for disabling the global TTL.
  • ttlPerCoordinate (type: Array of Object) - Overwrite the ttl for query operations whose selection contains a specific schema coordinate (e.g. Query.users). Useful if the selection of a specific field should reduce the TTL of the query operation.:
    • coordinate (type: String, required)
    • ttl (type: Float, required)
  • ignoredTypes (type: Array of String) - Skip caching of following the types.
  • idFields (type: Array of String) - List of fields that are used to identify the entity.
  • invalidateViaMutation (type: Boolean) - Whether the mutation execution result should be used for invalidating resources. Defaults to true
  • includeExtensionMetadata (type: Boolean) - Include extension values that provide useful information, such as whether the cache was hit or which resources a mutation invalidated.
  • sessionId (type: String) - Allows to cache responses based on the resolved session id. Return a unique value for each session. Creates a global session by default. Example;
sessionId: "{context.headers.userId}"
  • if (type: String) - Specify whether the cache should be used based on the context.
if: "context.headers.userId != null"
  • cacheKey (type: String) - Customize the behavior how the response cache key is computed from the documentString, variableValues, contextValue and sessionId. If the given string is interpolated as empty, default behavior is used. Example;
# Cache by specific value
cacheKey: "{variableValues.userId}"
 
# Cache by documentString
cacheKey: "{documentString}"
 
# Cache by operationName
cacheKey: "{operationName}"
 
# Cache by some header value
cacheKey: "{contextValue.headers.authorization}"
 
# Or combine two of each
cacheKey: "{contextValue.headers.authorization}-{operationName}"
  • shouldCacheResult (type: String) - Checks if the result should be cached.
shouldCacheResult: "result.errors.length > 0"

CodeSandBox Example

💡

You can learn more about GraphQL Live Query in its documentation. You can check out our example that uses live queries