Skip to content

graphql-generic connector

Ingest from any GraphQL API by putting the query directly in the manifest. GraphQL is a request shape on the universal REST engine (kind: graphql), not a separate connector — it reuses auth, rate-limiting, retry, hybrid JSONB+typed storage, and idempotent upsert.

YAML manifest

# 10-relay-basic.yaml — GraphQL source over the rest-generic engine: the query lives
# in the manifest, relay pagination re-injects endCursor into the variables.
#
# Apply and run:
#   lm apply -f docs/connectors/graphql/10-relay-basic.yaml
#   lm connector run shop
apiVersion: connectors.lumnik.io/v1
kind: Connector
metadata: { name: shop, connector_type: rest-generic, tags: [commerce] }
spec:
  base_url: https://myshop.example.com
  graphql_path: /api/graphql        # the single POST endpoint (default: /graphql)
  auth: { kind: bearer, token_env: SHOP_TOKEN }
  headers:                          # applied to every request (static or env)
    - { name: X-Api-Version, value: "2024-10" }
    - { name: X-Tenant,      value_env: SHOP_TENANT_HDR }
  default_rate_limit: 50/s
  endpoints:
    - id: orders                    # endpoint identifier (kebab-case)
      kind: graphql
      query: |
        query Orders($after: String) {
          orders(first: 100, after: $after) {
            edges { node { id name totalPrice updatedAt } }
            pageInfo { hasNextPage endCursor }
          }
        }
      variables: { someFlag: true } # static variables merged into every request
      pagination:
        kind: relay
        cursor_binding: variable     # cursor goes into variables, not the URL
        cursor_var: after
        cursor_path:   $.data.orders.pageInfo.endCursor
        has_more_path: $.data.orders.pageInfo.hasNextPage
        response_path: $.data.orders.edges[*].node
      incremental: { mode: full }
      target:
        kind: hybrid
        table: ext.shop_orders
        unique_key: $.id
        promote:
          - { name: total,      path: $.totalPrice, type: decimal }
          - { name: updated_at, path: $.updatedAt,  type: timestamptz }

Fields

Field Where Required Notes
graphql_path spec no The single POST endpoint; requests go to base_url + graphql_path (default /graphql)
kind: graphql endpoint no Marker only — an endpoint carrying a query is treated as GraphQL either way
query endpoint yes The GraphQL query document; declare the cursor variable it expects (e.g. $after)
variables endpoint no Static variables sent with every request; the relay cursor is injected on top
pagination endpoint yes Hard rule: kind: relay with cursor_binding: variable — anything else is refused at apply
cursor_var pagination yes The variable that receives endCursor (e.g. after)
cursor_path, has_more_path, response_path pagination yes JSONPaths to endCursor, hasNextPage, and the node list
target endpoint yes Same hybrid target as REST: table, unique_key, promote

path and method are implicit — the request is always a POST to graphql_path — and not required. Conversely, cursor_binding: variable is only valid here: on a non-graphql endpoint the validator refuses it (the cursor would ride a POST body that a plain GET never sends).

How it works

  • The query is POSTed as { "query": ..., "variables": ... } to base_url + graphql_path.
  • Relay pagination reads pageInfo { hasNextPage endCursor }; the endCursor is re-injected into variables.<cursor_var> for the next page (Connections spec).
  • response_path: ...edges[*].node extracts the clean node list (Jayway JSONPath projection).
  • GraphQL errors arrive as HTTP 200 with an errors[] array — any error fails the run and dead-letters the chunk (strict; no partial ingestion in this release). A 200 envelope with data: null and no errors is refused the same way — success MUST carry data.

Headers

spec.headers[] applies to every request: { name, value } (static) or { name, value_env } (resolved from the environment / a secret). Auth-injected headers win on a name clash.

Not yet supported (future)

Mutations, subscriptions, schema-introspection discovery, persisted queries, query batching, per-endpoint header overrides, and on_error: warn (partial-success ingestion).