Skip to content

GraphQL

A captured realm may declare one GraphQL source with graphql: one fixed HTTPS endpoint and a fixed set of persisted queries. Synth writes it to graphql/operations.yml, the only path the host reads.

credentials: {
"catalog-token": { kind: "bearer", description: "A read token for the product catalog." },
},
graphql: {
name: "catalog",
endpoint: "https://api.example.com/graphql",
auth: "bearer",
credential: "catalog-token",
operations: {
productById: {
document: "query($id: ID!) { product(id: $id) { name price } }",
variables: [{ name: "id", type: "string", required: true, maxLength: 64 }],
},
},
},

A handler calls each operation under the source’s name, passing only variables, and gets back the response’s data member:

import type { ProductHandler } from "../.embabel/realm.d.ts";
interface ProductData {
product: { name: string; price: number } | null;
}
export const product: ProductHandler = async (input, ctx) => {
const data = (await ctx.gateway.catalog.productById({
variables: { id: input.id },
})) as ProductData;
return { product: data.product };
};

The generated types give each operation an argument typed from its declared variables. A required variable is a required field, an optional one may be left out or null, an enum becomes a union of its values, and a variable the operation does not declare is a compile error. An operation with no variables takes no argument, or {}. The result is unknown, so narrow it before use.

Field Rule
name The gateway namespace: [A-Za-z_][A-Za-z0-9_-]{0,63}.
endpoint One fixed https:// origin on the default port, with no user info, query, fragment, . or .. segment, percent-encoded path or { placeholder.
credential A credential the realm declares under credentials. Naming it here counts as a reference.
auth "bearer", sent in Authorization, or "api-key", which needs header.
header "api-key" only: the header the key travels in, [A-Za-z][A-Za-z0-9-]{0,127}, and not one the host sets itself (Authorization, Cookie, Host, Idempotency-Key and the like).
headers Up to 16 fixed headers, each named X-..., used once and never the key’s own header, holding at most 1,024 printable ASCII characters with no ${ substitution.
operations 1 to 32 persisted queries, keyed by operation name, [A-Za-z_][A-Za-z0-9_-]{0,63}.
operations.<name>.document The query text, at most 16 KiB.
operations.<name>.variables Up to 32, each {name, type, required?, minLength?, maxLength?, enum?}. type is string (GraphQL String or ID), integer (Int), number (Float) or boolean. minLength and maxLength are whole numbers from 0 to 2048, and enum holds up to 128 strings, finite numbers or booleans.

What synth checks, which is also what the host refuses at load:

  • Each document is exactly one query. A mutation, subscription, fragment, ... spread, __schema or __type introspection, a second operation or a variable default is refused.
  • The document’s variable signature matches the variables list exactly: the same names, each once, each $name: Scalar with ! exactly where required is true. A list type such as [String!] is refused.
  • An operation’s call names (<name>.<operation>, <name>_<operation>, and the two camel-cased and joined with _) may not collide with an API operation’s, and may not be a host call’s own name such as channel_publish or cypher_query. The host refuses every operation in the realm over one such clash.
  • graphql/operations.yml is at most 64 KiB.

A query sends the secret the owner bound to the credential and is refused while nothing is bound. The host-side rules, including the checks on each supplied value and on the response, are in the captured GraphQL operation profile.