Resolvers
Resolvers execute the main logic in an executable GraphQLSchema
. These are functions that represent queries, mutations or subscriptions.
A Class annotated with @GraphQLObject()
will generate resolver fields for all its methods and properties. Resolver input parameters were discussed in the inputs section.
TODO: 2G BeforeResolver
Function Resolvers
You can use the @Query()
, @Mutation()
and @Subscription()
annotation to specify that a given method or function is a field in the given root object type (Query, Mutation or Subscription root objects).All function annotated with @Subscription()
should return a Stream
of values.
These annotations have the following parameters:
- name (default: the name of the method)
This will be the name of the GraphQL field.
- genericTypeName (default: null)
When the return type is a generic type, you can override the GraphQL type name with a custom String. Most generic types provide a default name composed from the type parameters (using GraphQLType.printableName
, for example), so this parameter is usually not required.
- TODO: 1G nullable return type
Class Resolvers
With @ClassResolver()
you can specify that a set of fields will be resolved by executing the methods of the decorated class. This allows you to group the fields into a separate class which give you a couple of nice features. Since in Dart all classes specify an "interface" this could be useful if you want to unit test the interface or implement the class resolver's API in other contexts. This also allows you to easily access common dependencies shared between the fields in the resolver class. Instead of using final dependencyName = dependencyRef.get(ctx);
in each field's resolver method body, you could create a field or getter within the class.
In order for Leto to have an instance of the resolver you need to provide a way of creating or getting the class resolver before executing any of the methods. We provide two main tool for that:
Resolver.ref
A static variable that implements BaseRef<FutureOr<Resolver?>>
. For example, a ScopedRef
. This will use the Ctx
of the field's resolver to access an instance of the class resolver with final FutureOr<Resolver> instance = Resolver.ref.get(ctx)!;
and then call the method instance.fieldName(...arguments)
where fieldName
is the name of the method.
instantiateCode
Available in: build.yaml
, ClassResolver
.
If you want to use a custom dependency injection library or method, you can provide an "instantiateCode" String which will be used as a getter to the class resolver instance. You can use the Ctx
of the field in the provided code with the ctx
variable and the resolver Dart class name with "{{name}}" as a template variable within the String. The value should be cast to a FutureOr<Resolver>
type if necessary. A variable of type Resolver
or Future<Resolver>
is fine, no need to explicitly add the as FutureOr<Resolver>
suffix.
final resolversTypeMap = <Type, Object Function(Ctx)>{
Resolver: (Ctx ctx) => Resolver(),
};
const diByMapType = ClassResolver(
instantiateCode: 'resolversTypeMap[{{name}}]!(ctx) as {{name}}',
);
class Resolver {
()
String getName() => '';
}
In the example above we first instantiate the annotation and then apply it to the class, this would allow you to reuse the diByMapType
annotation in multiple classes if you want to use the same dependency injection method for all of them.
- fieldName
Nested objects in resolvers.