⚠ Experimental ⚠
This feature may get big changes in future releases. Check the changelog for update notes.
GraphQL-Ruby 1.9.0 includes a new runtime module which you may use for your schema. Eventually, it will become the default.
It’s called GraphQL::Execution::Interpreter
and you can hook it up with use ...
in your schema class:
class MySchema < GraphQL::Schema
use GraphQL::Execution::Interpreter
# And, when you have updated your analyzers:
use GraphQL::Analysis::AST
end
Read on to learn more!
The new runtime was added to address a few specific concerns:
GraphQL::InternalRepresentation::Rewrite
) which could be very slow in some cases. In many cases, the overhead of that step provided no value.ctx
object for every field, even very simple fields that didn’t need any special tracking.You can opt in to the interpreter in your schema class:
class MySchema < GraphQL::Schema
use GraphQL::Execution::Interpreter
# And, after you have updated your analyzers:
use GraphQL::Analysis::AST
end
If you have a subscription root type, it will also need an update. Extend this new module:
class Types::Subscription < Types::BaseObject
# Extend this module to support subscription root fields with Interpreter
extend GraphQL::Subscriptions::SubscriptionRoot
end
Some Relay configurations must be updated too. For example:
- field :node, field: GraphQL::Relay::Node.field
+ add_field(GraphQL::Types::Relay::NodeField)
(Alternatively, consider implementing Query.node
in your own app, using NodeField
as inspiration.)
The new runtime works with class-based schemas only. Several features are no longer supported:
Proc-dependent field features:
GraphQL::Function
All these depend on the memory- and time-hungry per-field ctx
object. To improve performance, only method-based resolves are supported. If need something from ctx
, you can get it with the extras: [...]
configuration option. To wrap resolve behaviors, try Field Extensions,%20%5BTracing%5D(/queries/tracing),%20or%20%20%5BGraphQL::Schema::Resolver%5D(/fields/resolvers).
Query analyzers and irep_node
s
These depend on the now-removed Rewrite
step, which wasted a lot of time making often-unneeded preparation. Most of the attributes you might need from an irep_node
are available with extras: [...]
. Query analyzers can be refactored to be static checks (custom validation rules) or dynamic checks, made at runtime. The built-in analyzers have been refactored to run as validators.
For a replacement, check out:
GraphQL::Execution::Lookahead
for field-level info about child selectionsGraphQL::Analysis::AST
for query analysis which is compatible with the new interpreterrescue_from
This was built on middleware, which is not supported anymore. Stay tuned for a replacement.
.graphql_definition
and def to_graphql
The interpreter uses class-based schema definitions only, and never converts them to legacy GraphQL definition objects. Any custom definitions to GraphQL objects should be re-implemented on custom base classes.
GraphQL::Schema::Field#resolve_field
If you customized your base field’s resolution method, it needs an update. The interpreter calls a different method: #resolve(obj, args, ctx)
. There are two differences with the new method:
args
is plain ol’ Ruby Hash, with symbol keys, instead of a GraphQL::Query::Arguments
ctx
is a GraphQL::Query::Context
instead of a GraphQL::Query::Context::FieldResolutionContext
But besides that, it’s largely the same.
Maybe this section should have been called incompatibility 🤔.
See Directives.
GraphQL-Ruby has “analyzers” that run before execution and may reject a query. With the interpreter, you can use AST Analyzers to get better performance.
To make the migration, convert your previous analyzers to extend GraphQL::Analysis::AST::Analyzer
as described in the guide, then add to your schema:
use GraphQL::Analysis::AST
When you use both Interpreter
and Analysis::AST
, GraphQL-Ruby will skip the slow process of building irep_nodes
.
All analyzers must be migrated at once; running some legacy analyzers and some AST analyzers is not supported.
You can migrate to Interpreter
before migrating to Analysis::AST
. In that case, the irep_node
tree will still be constructed and used for analysis, even though it will not be used for execution.
Instead of a tree of irep_nodes
, the interpreter consumes the AST directly. This removes a complicated concept from GraphQL-Ruby (irep_node
s) and simplifies the query lifecycle. The main difference relates to how fragment spreads are resolved. In the previous runtime, the possible combinations of fields for a given object were calculated ahead of time, then some of those combinations were used during runtime, but many of them may not have been. In the new runtime, no precalculation is made; instead each object is checked against each fragment at runtime.
Instead of creating a GraphQL::Query::Context::FieldResolutionContext
for every field in the response, the interpreter uses long-lived, mutable objects for execution bookkeeping. This is more complicated to manage, since the changes to those objects can be hard to predict, but it’s worth it for the performance gain. When needed, those bookkeeping objects can be “forked”, so that two parts of an operation can be resolved independently.
Instead of calling .to_graphql
internally to convert class-based definitions to .define
-based definitions, the interpreter operates on class-based definitions directly. This simplifies the workflow for creating custom configurations and using them at runtime.