Queries, mutations and subscriptions
The three root operation types are normal object types with fields. gurafi installs a root type
when it has at least one field, so declaring an otherwise empty mutation root does not appear in
the schema.
Queries
query appends, so a query root can be declared across several modules. query_fields is the
same method with a name that states the intent.
Mutations
Top-level mutation fields execute serially, as the GraphQL specification requires. Nested fields may resolve concurrently unless the execution options disable concurrency.
Subscriptions
A subscription field returns a stream. gurafi wraps each event as the subscription root value
and the executor re-executes the selection for every event.
The stream item is the field's value type. The subscribe closure receives the coerced
arguments and the context, and runs once when the subscription starts.
When the event is an envelope rather than the field value, use field_map:
The stream yields Result<E, GraphQLError>. An Err event becomes a field error; a stream-level
failure ends the subscription. Use field_with_info when subscribe needs ResolveInfo; each
event resolver can also receive ResolveInfo with the corresponding *_with_info variant.
A subscription must select exactly one root field, and that field must define a subscribe
resolver. Setup validates arguments once; every event reuses the coerced arguments and runs the
field validator once per event.
Topics and filtering
There is no topic plugin: a subscription returns any Stream. A common pattern is a
tokio::sync::broadcast::Receiver wrapped with tokio_stream::wrappers::BroadcastStream, or a
futures::channel receiver filtered with .filter(...). The stream type already carries the
filtering and fan-out behaviour, so the schema does not need a separate topic registry.
Transporting subscriptions
A subscription is transport-independent; suku offers two ways to deliver it:
- SSE —
POSTthe operation withAccept: text/event-stream. Each event isevent: nextwith adata:JSON payload, followed byevent: complete. This works through ordinary HTTP proxies and CDNs and needs no upgrade. - WebSocket — connect with the
graphql-transport-wssubprotocol at the GraphQL path. Sendconnection_init, wait forconnection_ack, thensubscribewith anid; the server sendsnextper event andcompleteat the end. A clientcompletecancels the stream.
Use SSE when the client only needs a one-way stream and the deployment blocks upgrades. Use
WebSocket when many operations should share one connection, the client needs to cancel, or the
application already speaks the protocol. Both transports execute through the same Server, so
limits, prepared queries and error formatting are identical. Authentication for a WebSocket runs
once on the upgrade request; the connection then reuses that context.
Over HTTP this is POST with { "query": "...", "variables": {} } and
Accept: text/event-stream; over WebSocket it is the payload of a subscribe message. See
Serving over HTTP.
File uploads
GraphQL has no binary literal, so file inputs use the
multipart request specification:
the operation's variables contain null where a file belongs, and the file travels in a separate
multipart part. The server substitutes the file back into the variables before execution.
Model the file as a custom Upload scalar. In raw gql, a scalar parse_value accepts the
external value and the resolver downcasts it:
In gurafi, use Upload directly in a derived args or input struct. It is an input-only
scalar and is registered automatically:
The resolver receives the file with filename(), content_type() and bytes(). A plain
non-Option field is declared Upload!; use Option<Upload> when the file is optional.
Direct output use is a compile error because a file has no GraphQL response representation.
A client sends:
Files may be nested in input objects (variables.input.avatar) or lists
(variables.files.0), and a batch prefixes paths with the request index. Paths must point at an
existing null inside variables; a file cannot be injected anywhere else. See
Serving over HTTP for limits and error handling.
The transport buffers each request up to max_body_bytes (1 MiB by default). Raise it for real
uploads and set the reverse proxy to the same limit.
Root type names
The defaults are Query, Mutation and Subscription. Custom names appear in SDL and
introspection.
Field order and descriptions
Fields are emitted in declaration order. Use BuildOptions::sort_types(true) when stable
alphabetical type ordering matters for diffs or generated clients. Root descriptions are set with
BuildOptions::query_description, mutation_description and subscription_description.
Execution options
gql::ExecutionOptions controls the engine, not the schema:
suku exposes the same limits on Server. See Complexity and
Serving over HTTP.
Introspection
The schema supports the standard __schema, __type and __typename fields. Use
schema.sdl() to print the schema definition language document, or let a client run an
introspection query through suku.
