GraphQL Operations
A resolver often needs to run a subquery or submutation — a query or mutation against the root Query/Mutation type from inside the resolver. The usual way is to pass an inline selection string to ctx.query() / ctx.mutation(). When the same operation is used from more than one place, or you simply want it validated against the schema at build time, you can declare it once with @GraphQLOperation and hand the object to ctx.query() / ctx.mutation().
Experimental API
@GraphQLOperation is an experimental Viaduct API (@ExperimentalApi) and may change in a future release.
Declaring a query operation¶
A GraphQL operation is a Kotlin singleton object annotated with @GraphQLOperation, extending QueryFromAnnotation for a query (or MutationFromAnnotation for a mutation). The filmography tenant declares one that looks a character up by name and selects its identity fields through the shared CharacterIdentityFields named fragment:
@GraphQLOperation(
"""
query(${'$'}name: String!) {
searchCharacter(search: { byName: ${'$'}name }) {
...CharacterIdentityFields
}
}
"""
)
object CharacterByNameOperation : QueryFromAnnotation()
The document is validated against the schema at build time: it must contain exactly one operation, its type must match the base class (query for QueryFromAnnotation), variables must line up with the fields they feed, and any ...FragmentName spread must resolve to a @GraphQLFragment declared in the same tenant module.
Running it with ctx.query()¶
Pass the operation object — not a string — to ctx.query(), along with a map of its variables. Viaduct inlines the external fragments the operation spreads and executes it against the root Query type, returning a typed Query GRT whose getters mirror the schema:
@Resolver
class CharacterSummaryByNameQueryResolver : QueryResolvers.CharacterSummaryByName() {
override suspend fun resolve(ctx: Context): String? {
val result = ctx.query(CharacterByNameOperation, mapOf("name" to ctx.arguments.name))
val character = result.getSearchCharacter() ?: return null
val name = character.getName() ?: "Unknown"
val birthYear = character.getBirthYear() ?: "Unknown birth year"
return "$name ($birthYear)"
}
}
Try it in GraphiQL:
Declaring and running a mutation operation¶
Mutations work the same way, using MutationFromAnnotation and ctx.mutation(). This operation delegates to the existing updateCharacterName mutation:
@GraphQLOperation(
"""
mutation(${'$'}id: ID!, ${'$'}name: String!) {
updateCharacterName(id: ${'$'}id, name: ${'$'}name) {
...CharacterIdentityFields
}
}
"""
)
object RenameCharacterOperation : MutationFromAnnotation()
ctx.mutation() is only available from a resolver on the root Mutation type, so the consumer backs a Mutation field. The submutation shares the parent request's state — including the admin security context — so the updateCharacterName resolver it delegates to is still access-checked:
@Resolver
class RenameCharacterSummaryMutation : MutationResolvers.RenameCharacterSummary() {
override suspend fun resolve(ctx: Context): String? {
val result = ctx.mutation(
RenameCharacterOperation,
mapOf(
"id" to ctx.arguments.id,
"name" to ctx.arguments.name
)
)
val character = result.getUpdateCharacterName() ?: return null
val name = character.getName() ?: "Unknown"
val birthYear = character.getBirthYear() ?: "Unknown birth year"
return "$name ($birthYear)"
}
}
Run it with the security-access: admin header set (see Request Context):
When to reach for @GraphQLOperation¶
Use a declared operation when the selection is fixed (known at build time) and you want it validated against the schema, reused across resolvers, or simply kept out of the resolver body. If the fields you need aren't known until runtime, pass an inline selection string to ctx.query() / ctx.mutation() instead. And if the parent object or root Query already has the data you need before the resolver runs, prefer declaring it in the @Resolver annotation's objectValueFragment / queryValueFragment — see Subqueries for the full comparison.
For the API reference and validation rules, see the GraphQL Operations developer reference. For the fragment the operations above spread, see Named Fragments.