Skip to main content
The Tenant API (viaduct.api) provides the foundational interfaces and utilities for application developers to implement GraphQL resolvers, define modules, and interact with the Viaduct execution context.

Overview

The Tenant API is designed to be used by application developers (tenant developers) who write business logic on top of Viaduct. It provides:
  • Base interfaces for resolvers (ResolverBase, NodeResolverBase)
  • Execution contexts for accessing request data
  • Field value handling
  • Module definition interfaces
  • Utilities for working with connections and pagination

Core Interfaces

ResolverBase

Base interface for all field resolver classes.
interface ResolverBase<T>
T
type parameter
The return type of the resolve function
Package: viaduct.api Stability: Stable Usage: When you mark a field with @resolver in your GraphQL schema, Viaduct generates an abstract resolver class that extends ResolverBase. You implement the generated abstract class to provide custom resolution logic.
class UserResolver : UserQueryResolver() {
    override suspend fun resolve(ctx: FieldExecutionContext<...>): User {
        // Your resolution logic
    }
}

NodeResolverBase

Base interface for node resolver classes that resolve entities by Global ID.
interface NodeResolverBase<T : NodeObject>
T
type parameter
The return type implementing NodeObject (entities with global IDs)
Package: viaduct.api Stability: Stable Usage: When your GraphQL type implements the Node interface, Viaduct generates a node resolver base class. Implement it to resolve entities by their Global ID:
class UserNodeResolver : UserNodeResolverBase() {
    override suspend fun resolve(ctx: NodeExecutionContext<User>): User? {
        val userId = ctx.id.internalID
        return fetchUserById(userId)
    }
}

SelectiveResolver

Marker interface for node resolvers that vary their response based on the requested selection set.
interface SelectiveResolver
Package: viaduct.api Stability: Stable Effects of implementing this interface:
  • Enables access to ctx.selections() within resolver methods
  • Changes caching behavior to match by ID + selection set (instead of ID-only)
When to use:
  • Use when the resolver needs to optimize by fetching only requested fields
  • Don’t use if the resolver always returns the same data for a given ID
Example:
class OptimizedUserResolver : UserNodeResolverBase(), SelectiveResolver {
    override suspend fun resolve(ctx: SelectiveNodeExecutionContext<User>): User? {
        val selections = ctx.selections()
        // Fetch only the fields that were selected
        return fetchUserWithSelections(ctx.id.internalID, selections)
    }
}

TenantModule

Interface that all Viaduct modules must implement.
interface TenantModule {
    val metadata: Map<String, String>
    val packageName: String
}
metadata
Map<String, String>
Metadata to be associated with this module
packageName
String
The package name for the module (defaults to the Java package name)
Package: viaduct.api Stability: Stable Usage: Viaduct code generation creates a module class for each module. You typically don’t implement this directly.
object FilmographyModule : TenantModule {
    override val metadata = mapOf(
        "version" to "1.0",
        "description" to "Star Wars filmography data"
    )
}

Execution Contexts

Execution contexts provide access to request data, arguments, selections, and utilities within resolvers.

ExecutionContext

Generic base context for all resolvers and variable providers.
interface ExecutionContext {
    fun <T : NodeObject> globalIDFor(type: Type<T>, internalID: String): GlobalID<T>
    val requestContext: Any?
}
globalIDFor
function
Creates a Global ID for a given type and internal IDParameters:
  • type: Type<T> - The reflection type (e.g., User.Reflection)
  • internalID: String - Your internal identifier
Returns: GlobalID<T> - Type-safe Global IDExample:
val userId = ctx.globalIDFor(User.Reflection, "123")
requestContext
Any?
Value set as ExecutionInput.requestContext by the service engineer. Use this to access deployment-specific request context like authentication, tracing, etc.

FieldExecutionContext

Execution context provided to field resolvers.
interface FieldExecutionContext<O : Object, Q : Query, A : Arguments, R : CompositeOutput> : 
    BaseFieldExecutionContext<Q, A, R>
O
type parameter
The parent object type containing this field
Q
type parameter
The query type for data fetching
A
type parameter
The arguments type for this field
R
type parameter
The return type (composite output)
Key Properties and Methods:
objectValue
O
The parent object value with selections from objectValueFragment populated. Access is lazy - fields resolve on demand.Example:
val userName = ctx.objectValue.name
getObjectValue()
suspend () -> O
Returns a synchronously-accessible version of objectValue where all selections have been eagerly resolved. Use when you need all object data available before proceeding.
queryValue
Q
Access to root Query type selections. Useful for fetching additional data.
arguments
A
Field arguments provided by the caller
selections()
SelectionSet<R>
The selection set requested by the caller for the return type

NodeExecutionContext

Execution context for Node resolvers (non-selective).
interface NodeExecutionContext<R : NodeObject> : ResolverExecutionContext<Query>
id
GlobalID<R>
The Global ID of the node being resolvedExample:
val internalId = ctx.id.internalID
val user = fetchUserById(internalId)

SelectiveNodeExecutionContext

Extended context for Node resolvers implementing SelectiveResolver.
interface SelectiveNodeExecutionContext<R : NodeObject> : NodeExecutionContext<R>
selections()
SelectionSet<R>
The selection set requested for this node. Only available when the resolver implements SelectiveResolver.

ConnectionFieldExecutionContext

Specialized execution context for Relay-style connection fields with pagination.
interface ConnectionFieldExecutionContext<O, Q, A : ConnectionArguments, R : Connection<*, *>> : 
    FieldExecutionContext<O, Q, A, R>
Package: viaduct.api.context Stability: Experimental Usage: Used for fields marked with @connection that return paginated results. The arguments type A automatically includes pagination parameters like first, after, last, before.

MutationFieldExecutionContext

Execution context for root Mutation type field resolvers.
interface MutationFieldExecutionContext<Q, M : Mutation, A, R> : 
    BaseFieldExecutionContext<Q, A, R>
mutation()
suspend (String, Map<String, Any?>) -> M
Executes selections on the root Mutation typeParameters:
  • selections: String - GraphQL selections to execute
  • variables: Map<String, Any?> - Optional variables (defaults to empty)
Example:
val result = ctx.mutation("{ createUser(input: $input) { id name } }")

Field Values

FieldValue

Represents the result of resolving a GraphQL field, which may be either a value or an error.
sealed interface FieldValue<out T> {
    fun get(): T
    val isError: Boolean
}
get()
T
Returns the value on success, or throws an exception for an error value
isError
Boolean
Whether this represents an error value
Companion Object Methods:
ofValue(value)
<T> (T) -> FieldValue<T>
Constructs a FieldValue that resolved successfullyExample:
return FieldValue.ofValue(user)
ofError(error)
(Exception) -> FieldValue<Nothing>
Constructs a FieldValue that resolved with an error. Use FieldError to customize the GraphQL error response.Example:
return FieldValue.ofError(FieldError("User not found"))

See Also

Build docs developers (and LLMs) love