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").Type("!") with full type safety
Variable DeclarationObject-style in query headerObject spread: { ...$var(...), ...$var(...) }
Field SelectionsImplicit object syntaxObject spread: ({ ...f.id(), ...f.name() })
Type CheckingRequires external codegen stepCompile-time validation
OutputRuntime parsing needed.document for query
Object Spread API

soda-gql uses object spread syntax for both variable declarations and field selections. This design enables better type inference and explicit field merging.

Operation Types

soda-gql supports three operation types:

// Query - fetch data
gql.default(({ query, $var }) =>
  query.operation({ name: "GetUser", variables: { ... }, fields: ({ f, $ }) => ({ ... }) })
);

// Mutation - modify data
gql.default(({ mutation, $var }) =>
  mutation.operation({ name: "CreateUser", variables: { ... }, fields: ({ f, $ }) => ({ ... }) })
);

// Subscription - real-time updates (planned)
gql.default(({ subscription, $var }) =>
  subscription.operation({ name: "UserUpdated", variables: { ... }, fields: ({ 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").ID("!") },         // Variable declarations
    fields: ({ f, $ }) => ({
      // Field selections
      ...f.user({ id: $.userId })(({ f }) => ({
        ...f.id(),
        ...f.name(),
        ...f.email(),
      })),
    }),
  }),
);

Operation Options

OptionTypeDescription
namestringRequired. The GraphQL operation name
variables{ ...$var(...) }Object spread of variable declarations
metadatafunctionOptional. Runtime metadata (see Metadata)
fieldsfunctionRequired. Field selection builder function

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(),
    })),
  })),
})

Spreading Fragments

Spread fragments to reuse field selections:

import { userFragment } from "./user.fragment";

export const getUserQuery = gql.default(({ query, $var }) =>
  query.operation({
    name: "GetUser",
    variables: {
      ...$var("userId").ID("!"),
      ...$var("includeEmail").Boolean("?"),
    },
    fields: ({ f, $ }) => ({
      ...f.user({ id: $.userId })(({ f }) => ({
        // Spread fragment with variable passing
        ...userFragment.spread({ 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
//   }
// }

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").CreateUserInput("!") },
    fields: ({ 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" },
  },
});

Next Steps