stackql / stackql/any-sdk

[FEATURE] GraphQL mutation support (operationType, variables payload, errors-in-200 selection)

Open
#142 0 comments 0 reactions 1 assignee View on GitHub

@jeffreyaven is already working on this.

Since Sep 14, 2026.

enhancement
Dominant language
Go
Stars
0
Forks
1
Avg merge
14h 11m
Merged PRs (30d)
8

Description

Summary

any-sdk currently supports GraphQL for reads only. The x-stackQL-graphQL extension is modelled by standardGraphQL (internal/anysdk/graphql.go) with id, query, url, httpVerb, cursor, and responseSelection - no operation type and no variables. The only execution path is StandardGQLReader (pkg/graphql/graphql.go), a paginating read loop (cursor strategies: cursor_after, keyset, offset, page_info) whose renderQuery() templates the cursor splice into the query string and posts { "query": "..." }. The REST mutation machinery (isMutation in public/formulation and public/providerinvokers/anysdkhttp) never consults GetGraphQL(), and the static analyzer short-circuits GraphQL methods entirely:

// public/discovery/static_analyzer.go:1448
graphQL := method.GetGraphQL()
if graphQL != nil {
    return result // TODO: GraphQL methods analysis
}

The consequence is that all GraphQL-backed providers are SELECT-only by engine constraint, not by provider choice. This issue proposes first-class GraphQL mutation support.

Motivating consumers
  • gitlab - the intended canonical reference implementation: a strictly Relay-conventional mutation surface (single input object argument, <entity>Create / <entity>Update / <entity>Destroy naming, errors: [] payloads inside HTTP 200), free full-lifecycle CI via the gitlab-ce container. A read-only provider for a DevOps platform is a visible gap (issues, MRs, labels, CI variables are write-expected resources).
  • runpod - GraphQL-only vendor whose control operations (pod deploy/start/stop/terminate) are mutations; without this feature the provider is inventory-only.
  • railway - GraphQL-only vendor; deploys/restarts/scaling are mutations.
  • Write phases of newrelic (NerdGraph: alert policies, dashboards, tagging) and buildkite.
Proposed design
  1. Model (internal/anysdk/graphql.go, loader in internal/anysdk/loader.go via ExtensionKeyGraphQL):

    • operationType (string, optional): absent or query -> existing reader behaviour; mutation -> one-shot executor. Back-compat: every existing spec is untouched.
    • variables (map, optional): a template map binding method parameters to a GraphQL variables payload, rendered with the same text/template inputs the query template receives. Present -> the request body is { "query": ..., "variables": { ... } }.
    • errorSelection (GraphQLElement, optional): a jsonPath whose resolution to a non-empty array or non-null value marks the call failed, regardless of HTTP 200. This is the GraphQL errors-in-200 convention (both the top-level errors array and payload-level userErrors/errors fields, selectable per method).
    • responseSelection retains its current meaning for the mutation payload projection.
  2. Executor (pkg/graphql/): a one-shot GQLExecutor alongside StandardGQLReader - no cursor loop, renders query + variables, single request, applies errorSelection then responseSelection. Reuse the client, logging context (ContextWithHTTPLogger), and stream_transform hooks the reader already has.

    • Note: variables support should also be made available to the read path in a follow-up, replacing the current escape-and-embed rendering in renderQuery() (pkg/graphql/graphql.go:649) for methods that declare variables - a robustness improvement independent of mutations.
  3. Invocation wiring: route GraphQL methods with operationType: mutation through the mutation path (the isMutation plumbing in public/formulation/formulation.go and public/providerinvokers/anysdkhttp/invoker.go) so INSERT/UPDATE/DELETE/EXEC verb mappings in sqlVerbs become legal for GraphQL methods.

  4. Static analysis (public/discovery/static_analyzer.go): retire the TODO at line 1448 with GraphQL method checks - query parses, template placeholders resolvable from declared parameters, operationType: mutation methods carry errorSelection (warning if absent), responseSelection present for selectable methods, cursor config forbidden on mutations.

Spec shape (target)
x-stackQL-graphQL:
  url: https://gitlab.com/api/graphql
  httpVerb: POST
  operationType: mutation
  query: >
    mutation($input: CreateIssueInput!) {
      createIssue(input: $input) {
        issue { iid title state webUrl }
        errors
      }
    }
  variables:
    input:
      projectPath: '{{ .full_path }}'
      title: '{{ .title }}'
      description: '{{ .description }}'
  responseSelection:
    jsonPath: '$.data.createIssue.issue'
  errorSelection:
    jsonPath: '$.data.createIssue.errors'
Acceptance criteria
  • Existing GraphQL read specs (including the in-repo fixture internal/anysdk/testdata/registry/src/github/v0.3.1/services/scim.yaml) load and execute byte-identically - operationType absent implies current behaviour.
  • A mutation method loads via ExtensionKeyGraphQL, executes one-shot with a variables payload, projects responseSelection on success.
  • A populated errorSelection result surfaces as a call failure (with the selected content in the error), never as rows - covered by a negative test.
  • Top-level GraphQL errors on HTTP 200 also surfaces as failure.
  • Static analyzer covers GraphQL methods per point 4; the line 1448 TODO is removed.
  • Unit tests in pkg/graphql for the executor (success, payload errors, top-level errors, template resolution failure).
Out of scope
  • GraphQL subscriptions (streaming - excluded per standing policy).
  • Automatic mutation generation from introspection (provider tooling concern - @stackql/provider-utils).
  • Cursor/pagination interaction with mutations (forbidden by analyzer rule instead).

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.