GraphQL
Compiled SDL plus query executor — every entity, every relation, every operation, generated atomically with REST.
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 introspectionGenerated 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-abcConnection-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..."
}
}]
}