Vadyl's GraphQL surface is compiled from the same canonical entity model that drives REST, gRPC, and SDK projection. Schema types, connection types, queries, mutations, and subscriptions are all generated; access enforcement, masking, and pagination work the same way as the REST surface.

Endpoints

POST /graphql           # execute queries / mutations
GET  /graphql           # GraphiQL playground (in dev)
GET  /graphql/schema    # SDL schema introspection

Generated types

For an entity Order:

type Order {
  id:         ID!
  total:      Decimal!
  currency:   String!
  status:     OrderStatus!
  customer:   Customer!
  createdAt:  DateTime!
}

enum OrderStatus { PENDING PAID FULFILLED REFUNDED }

type OrderConnection {
  edges:    [OrderEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type OrderEdge { node: Order! cursor: String! }

input OrderFilter {
  status: { eq: OrderStatus, in: [OrderStatus!] }
  total:  { gt: Decimal, lt: Decimal, between: [Decimal!] }
  customer: { id: { eq: ID } }
}

Query: list orders

The same query, expressed across clients:

query RecentOrders($filter: OrderFilter, $first: Int!) {
  orders(filter: $filter, first: $first, sort: [{ field: createdAt, direction: DESC }]) {
    edges {
      node {
        id
        total
        status
        customer { id name email }
      }
      cursor
    }
    pageInfo { hasNextPage endCursor }
    totalCount
  }
}

Mutation: create

type Mutation {
  createOrder(input: OrderCreateInput!): Order!
  updateOrder(id: ID!, input: OrderUpdateInput!): Order!
  deleteOrder(id: ID!): DeleteResult!
}
mutation CreateOrder($input: OrderCreateInput!) {
  createOrder(input: $input) {
    id
    status
    total
    createdAt
  }
}

# Headers:
#   Idempotency-Key: signup:sess-abc

Connection-style pagination

query Recent {
  orders(filter: { status: { eq: PAID } }, first: 25) {
    edges { node { id total status } cursor }
    pageInfo { hasNextPage endCursor }
    totalCount
  }
}

Cursors are stable across concurrent writes — use them for any large dataset. first/after for forward,last/before for backward.

Field selection and masking

Field-level access is enforced inside the GraphQL executor. Fields the actor cannot read are not included in the response — they're dropped from the projection. The executor never returns a field marked unreadable by the access engine, even if requested.

Subscriptions

subscription OnOrderPaid {
  orderChanged(filter: { status: { eq: PAID } }) {
    kind
    entityId
    changedFields
  }
}

Subscriptions reuse the canonical realtime substrate — change events carry field names only (never values, defense-in-depth). The subscriber re-reads through CRUD with proper access enforcement to get values.

Errors

{
  "data": { "orders": null },
  "errors": [{
    "message":   "Access denied",
    "path":      ["orders"],
    "extensions": {
      "code":         "ACCESS_DENIED",
      "reasonCode":   "Access.DeniedByMissingClaim",
      "correlationId":"01HXY..."
    }
  }]
}