kaiv for GraphQL Users — A Cookbook

for kaiv 0.4.0

1 Introduction

You think in SDL — types, non-null bangs, enums, a schema that fronts every byte your API serves. This cookbook maps GraphQL’s schema language onto kaiv, and fills the gap SDL leaves open: GraphQL has no data format. Your types constrain responses in flight, and nothing else — the JSON on disk, the fixture in the repo, the export in the bucket are all untyped the moment they leave the server.

kaiv gives an SDL-shaped contract a life outside the API: the schema converts, documents validate against it at rest, and the same canonical form exports to JSON, YAML, XML, CBOR, TOML, and the binary schema formats. GraphQL keeps serving queries; kaiv keeps the data honest everywhere else.

Every example is a verified transcript of the reference kaiv tool (cargo install kaiv-cli), run against the fixture files shipped beside this document.

2 Your SDL Converts

A schema with the usual shapes — non-null scalars, an enum, a nested object type, a list, a nullable field:

enum Status {
  DRAFT
  PUBLISHED
}

type Author {
  name: String!
  handle: String!
}

type Post {
  title: String!
  words: Int!
  status: Status!
  author: Author!
  tags: [String!]!
  summary: String
}

Convert it, picking the root type:

$ kaiv import-schema --graphql --name acme/post --message Post post.graphql
.!kaivschema 1 acme/post

title=
!int[-2147483648,2147483647]
words=
!str{DRAFT,PUBLISHED}
status=
/author::name=
/author::handle=
/@tags;=
!null|str
summary?=

Reading it back: GraphQL Int is specified as signed 32-bit, and the conversion says so as a value constraint — the exact range, on the line. The enum became an enumeration with teeth. Non-null ! became required (kaiv’s default); nullable summary became optional with an explicit !null alternative. The Author object flattened to namespace fields; the list became a vector. Nothing to unlearn — the conversion reads like the SDL, minus the braces.

3 Types for Data at Rest

Now the part GraphQL cannot do. Author a document — or receive one as API-response JSON — and hold it to the contract off the wire:

$ kaiv import-schema --graphql --name acme/post --message Post post.graphql > post.saiv
$ printf '{"title":"The Hub","words":900,"status":"PUBLISHED","author":{"name":"Ada","handle":"ada"},"tags":["kaiv"],"summary":null}' | kaiv import --json | kaiv build > post.daiv
$ kaiv validate post.daiv post.saiv
pass
$ printf '{"title":"The Hub","words":900,"status":"ARCHIVED","author":{"name":"Ada","handle":"ada"},"tags":["kaiv"],"summary":null}' | kaiv import --json | kaiv build > arch.daiv
$ kaiv validate arch.daiv post.saiv
kaiv: ConstraintViolationError: ::status=ARCHIVED (type !str) violates {DRAFT,PUBLISHED} (line 4)

Field, value, the violated enumeration, and the line — on a fixture file, a cached response, an export; no server in sight. Exit code 1; pass and exit 0 otherwise.

One equivalence worth naming: GraphQL types are implicitly strict — a response never carries fields the type does not declare. A kaiv strict schema is the same assertion made explicit, and the converted schema can adopt it by adding strict to its header line.

4 The Mapping

GraphQL SDL kaiv
type namespace / document
nested object type namespace fields /author::name=
String! required str requiredness is the default
String !null|str optional absence explicit
Int !int + 32-bit range per the GraphQL spec
enum enum constraint {DRAFT,PUBLISHED}
[String!]! ;= vector
union tagged union over types, not object types
interface schema composition allOf-style
"""docs""" // doc comments
@deprecated mapping documents instead

And beyond the mapping, the kaiv side adds what SDL has no syntax for: value patterns, numeric ranges of your choosing, length bounds, units, per-value provenance — see the sibling cookbooks for the machinery.

5 When to Stay with GraphQL

An honest map marks the roads not taken. GraphQL remains the right choice — indeed, has no kaiv equivalent — for:

kaiv is not a GraphQL replacement — it is where your SDL’s discipline extends when the data leaves the endpoint: fixtures, seeds, caches, exports, archives, and every file a contract should cover but a resolver never sees.

6 Where Next

From here: