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:

AspectGraphQLsoda-gql
DefinitionString-based query languageTypeScript builder functions
Variables$name: Type! syntax$var("name").scalar("Type:!") with full type safety
Variable DeclarationObject-style in query headerArray-based: [$var(...), $var(...)]
Field SelectionsImplicit object syntaxArray-based: [f.id(), f.name()]
Type CheckingRequires external codegen stepCompile-time validation
OutputRuntime 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

OptionTypeDescription
namestringRequired. The GraphQL operation name
variables$var[]Array of variable declarations
metadatafunctionOptional. 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