gleamql/operation
Operation builders for constructing GraphQL queries and mutations.
This module provides functions for building complete GraphQL operations with variable definitions and root fields. It supports both single and multiple root field operations.
Basic Usage - Single Root Field
import gleamql/operation
import gleamql/field
let country_op =
operation.query("CountryQuery")
|> operation.variable("code", "ID!")
|> operation.field(country_field())
Multiple Root Fields
Query multiple fields at the root level while maintaining type safety:
operation.query("GetDashboard")
|> operation.variable("userId", "ID!")
|> operation.root(fn() {
use user <- field.field(user_field())
use posts <- field.field(posts_field())
use stats <- field.field(stats_field())
field.build(#(user, posts, stats))
})
This generates clean GraphQL without wrapper fields:
query GetDashboard($userId: ID!) {
user { ... }
posts { ... }
stats { ... }
}
Types
A complete GraphQL operation (query or mutation) with its decoder.
pub opaque type Operation(a)
Builder for constructing operations.
pub opaque type OperationBuilder(a)
The type of GraphQL operation.
pub type OperationType {
Query
Mutation
}
Constructors
-
Query -
Mutation
A variable definition for a GraphQL operation.
pub type VariableDef {
VariableDef(name: String, type_def: String)
}
Constructors
-
VariableDef(name: String, type_def: String)
Values
pub fn anonymous_mutation() -> OperationBuilder(a)
Create an anonymous mutation operation.
Example
operation.anonymous_mutation()
|> operation.field(create_post_field())
|> operation.build()
pub fn anonymous_query() -> OperationBuilder(a)
Create an anonymous query operation.
Anonymous operations have no name and are useful for simple queries.
Example
operation.anonymous_query()
|> operation.field(countries_field())
|> operation.build()
pub fn build_variables(
operation: Operation(a),
values: List(#(String, json.Json)),
) -> json.Json
Build the variables JSON object for the GraphQL request.
Takes a list of variable name/value pairs and constructs the variables object to send with the request.
pub fn decoder(operation: Operation(a)) -> decode.Decoder(a)
Get the decoder from an operation.
This decoder will automatically unwrap the “data” field from the GraphQL response.
pub fn field(
builder: OperationBuilder(a),
root_field: field.Field(a),
) -> Operation(a)
Set the root field for the operation and build the final Operation.
This completes the operation builder and generates the GraphQL query string. Fragments used in the field tree are automatically collected and included in the operation.
Example
operation.query("GetCountry")
|> operation.variable("code", "ID!")
|> operation.field(country_field())
|> operation.build()
pub fn fragment(
builder: OperationBuilder(a),
frag: fragment.Fragment(b),
) -> OperationBuilder(a)
Add a fragment definition to the operation (optional).
Note: As of version 1.0.0, fragments are automatically collected when
you use fragment.spread(), so this function is optional. You only need
to use it if you want to explicitly register a fragment that isn’t used in
the current operation’s fields.
For most use cases, simply use fragment.spread() in your field selections
and the fragment will be automatically included.
Example (modern approach - auto-collection)
import gleamql/fragment
let user_fields = fragment.on("User", "UserFields", fn() {
use id <- field.field(field.id("id"))
use name <- field.field(field.string("name"))
field.build(User(id:, name:))
})
// No need to call operation.fragment() - it's auto-collected!
operation.query("GetUser")
|> operation.variable("id", "ID!")
|> operation.field(
field.object("user", fn() {
use user_data <- field.field(fragment.spread(user_fields))
field.build(user_data)
})
)
Example (legacy approach - manual registration)
// You can still manually register if needed
operation.query("GetUser")
|> operation.fragment(user_fields) // Optional - for backwards compatibility
|> operation.variable("id", "ID!")
|> operation.field(user_field())
pub fn mutation(name: String) -> OperationBuilder(a)
Create a named mutation operation.
Example
operation.mutation("CreatePost")
|> operation.variable("input", "CreatePostInput!")
|> operation.field(create_post_field())
|> operation.build()
pub fn query(name: String) -> OperationBuilder(a)
Create a named query operation.
Example
operation.query("GetCountry")
|> operation.variable("code", "ID!")
|> operation.field(country_field())
|> operation.build()
pub fn root(
builder: OperationBuilder(b),
root_builder: fn() -> field.ObjectBuilder(a),
) -> Operation(a)
Set multiple root fields for the operation using a builder pattern.
This allows you to query multiple root fields in a single operation
while maintaining full type safety. The builder pattern is identical to
field.object(), but the generated GraphQL will not wrap the fields
in an additional object.
Example
pub type UserAndPosts {
UserAndPosts(user: User, posts: List(Post))
}
operation.query("GetData")
|> operation.variable("userId", "ID!")
|> operation.root(fn() {
use user <- field.field(
field.object("user", user_builder)
|> field.arg("id", "userId")
)
use posts <- field.field(
field.list(field.object("posts", post_builder))
|> field.arg_int("limit", 10)
)
field.build(UserAndPosts(user:, posts:))
})
Generates:
query GetData($userId: ID!) {
user(id: $userId) { ... }
posts(limit: 10) { ... }
}
Single Root Field
You can also use root() with a single field (though field() is simpler):
operation.root(fn() {
use user <- field.field(user_field())
field.build(user)
})
Backward Compatibility
The existing field() function continues to work for single-field operations.
Use root() when you need multiple root fields or want a consistent API.
pub fn to_string(operation: Operation(a)) -> String
Get the GraphQL query string from an operation.
pub fn variable(
builder: OperationBuilder(a),
name: String,
type_def: String,
) -> OperationBuilder(a)
Add a variable definition to the operation.
Variables allow you to parameterize your operations and reuse them with different values.
Example
operation.query("GetCountry")
|> operation.variable("code", "ID!") // Non-null ID
|> operation.variable("lang", "String") // Optional String
The type definition should be a valid GraphQL type:
- Scalars:
"String","Int","Float","Boolean","ID" - Non-null:
"String!","ID!" - Lists:
"[String]","[ID!]!" - Custom types:
"CreatePostInput!","[UserInput!]"
pub fn variable_names(operation: Operation(a)) -> List(String)
Get the list of variable names defined in the operation.
This is useful for knowing which variables need values at send time.