Operations
Operations are complete GraphQL queries, mutations, or subscriptions. They define variables, select fields, and produce executable GraphQL documents.
How soda-gql Operations Differ from GraphQL
In standard GraphQL, operations are written as query strings:
query GetUser($id: ID!) {
user(id: $id) {
id
name
}
}
soda-gql operations are TypeScript functions:
| Aspect | GraphQL | soda-gql |
|---|
| Definition | String-based query language | TypeScript builder functions |
| Variables | $name: Type! syntax | $var("name").scalar("Type:!") with full type safety |
| Variable Declaration | Object-style in query header | Array-based: [$var(...), $var(...)] |
| Field Selections | Implicit object syntax | Array-based: [f.id(), f.name()] |
| Type Checking | Requires external codegen step | Compile-time validation |
| Output | Runtime parsing needed | .document for query, .parse() for results |
Array-Based API
soda-gql uses arrays for both variable declarations and field selections. This design enables better type inference and explicit ordering.
Operation Types
soda-gql supports three operation types:
// Query - fetch data
gql.default(({ query }, { $var }) =>
query.operation({ name: "GetUser", variables: [...] }, ({ f, $ }) => [...])
);
// Mutation - modify data
gql.default(({ mutation }, { $var }) =>
mutation.operation({ name: "CreateUser", variables: [...] }, ({ f, $ }) => [...])
);
// Subscription - real-time updates (planned)
gql.default(({ subscription }, { $var }) =>
subscription.operation({ name: "UserUpdated", variables: [...] }, ({ f, $ }) => [...])
);
Defining an Operation
A complete operation definition includes:
import { gql } from "@/graphql-system";
export const getUserQuery = gql.default(({ query }, { $var }) =>
query.operation(
{
name: "GetUser", // Operation name
variables: [$var("userId").scalar("ID:!")], // Variable declarations
},
({ f, $ }) => [
//
// Field selections
f.user({ id: $.userId })(({ f }) => [
//
f.id(),
f.name(),
f.email(),
]),
],
),
);
Operation Options
| Option | Type | Description |
|---|
name | string | Required. The GraphQL operation name |
variables | $var[] | Array of variable declarations |
metadata | function | Optional. Runtime metadata (see Metadata) |
Field Selections
Field selections in operations work the same as in fragments:
({ f, $ }) => [
//
// Scalar fields
f.id(),
f.createdAt(),
// Fields with arguments
f.posts({ limit: 10 })(({ f }) => [
//
f.id(),
f.title(),
]),
// Nested selections
f.user({ id: $.userId })(({ f }) => [
//
f.id(),
f.profile()(({ f }) => [
//
f.avatarUrl(),
f.bio(),
]),
]),
]
Embedding Fragments
Embed fragments to reuse field selections:
import { userFragment } from "./user.fragment";
export const getUserQuery = gql.default(({ query }, { $var }) =>
query.operation(
{
name: "GetUser",
variables: [
//
$var("userId").scalar("ID:!"),
$var("includeEmail").scalar("Boolean:?"),
],
},
({ f, $ }) => [
//
f.user({ id: $.userId })(({ f }) => [
//
// Embed fragment with variable passing
userFragment.embed({ includeEmail: $.includeEmail }),
]),
],
),
);
When a fragment has variables, you must pass values for them. These can be:
- Literal values:
{ includeEmail: true }
- Operation variables:
{ includeEmail: $.includeEmail }
Operation Output
Every operation provides two key properties:
.document
The compiled GraphQL document string, ready to send to a GraphQL server:
console.log(getUserQuery.document);
// query GetUser($userId: ID!) {
// user(id: $userId) {
// id
// name
// email
// }
// }
.parse()
Type-safe response parsing that validates and transforms the result:
const response = await graphqlClient({
document: getUserQuery.document,
variables: { userId: "123" },
});
// Parse with full type inference
const data = getUserQuery.parse(response);
// data.user.id, data.user.name, etc. are fully typed
Type Inference
Extract TypeScript types from operations:
// Input type (variables required for this operation)
type GetUserVariables = typeof getUserQuery.$infer.input;
// { userId: string }
// Output type (parsed response structure)
type GetUserResult = typeof getUserQuery.$infer.output.projected;
// { user: { id: string; name: string; email: string } }
Mutations
Mutations follow the same pattern as queries:
export const createUserMutation = gql.default(({ mutation }, { $var }) =>
mutation.operation(
{
name: "CreateUser",
variables: [$var("input").scalar("CreateUserInput:!")],
},
({ f, $ }) => [
//
f.createUser({ input: $.input })(({ f }) => [
//
f.id(),
f.name(),
]),
],
),
);
// Usage
const result = await graphqlClient({
document: createUserMutation.document,
variables: {
input: { name: "Alice", email: "alice@example.com" },
},
});
const data = createUserMutation.parse(result);
Next Steps