Relay
gurafi-relay adds the Relay conventions a code-first schema normally hand-writes:
- typed global IDs
- the shared
Nodeinterface andnode/nodesroot fields - connections with cursors and
PageInfo - mutation payloads with
clientMutationId
It is a thin layer over public gurafi APIs and has no async-runtime dependency.
Global IDs
GlobalId<T> is an ID! scalar that encodes Base64("TypeName:localId"):
The decode checks Base64, UTF-8, padding, the TypeName:localId shape and the type name. A
User ID cannot be passed where a Post ID is expected.
Node
The first registration installs:
Every later node call adds a type to the same dispatcher. The object implements Node; an
id: ID! field returning the encoded global ID is added unless the object already declares one.
Loaders:
load_one/load_one_resultload_many/load_many_result
If only one is supplied, the other is derived from it. The fallback from load_many to
load_one is bounded to avoid an unbounded fan-out from a large nodes request.
Request cache:
With an accessor, node/nodes cache results by type and local ID for the request. Without one,
nodes still deduplicates within a single call.
Local IDs
Bare local IDs are rejected by default. For a migration period, opt in:
The fallback tries a value against every registered node loader, so a client can resolve a type it did not name. Enable it only while clients still send local IDs. The setting is shared with the installed root fields and can be changed after registration.
Connections
Connection type names are explicit. Register the connection first, then attach it to a field.
This creates:
Connection::from_slice applies first / after / last / before with the Relay
ApplyCursorsToEdges rules and computes hasNextPage / hasPreviousPage from the filtered
window. Connection::from_slice_by_index uses the item index as an opaque cursor;
Connection::empty() starts an empty page.
PageInfo contains hasNextPage, hasPreviousPage, startCursor and endCursor. Cursors are
opaque Base64 strings by default. A cursor that is not in the list is an error rather than a
silent restart.
Mutations
A Relay mutation generates <Field>Payload with a result: T! field and a configurable
clientMutationId:
The generated shape is:
ClientMutationId::Omit, Optional and Required control presence. T = Option<Post> makes
result nullable. Async variants are available with mutation_async_with; mutation is the
sync convenience form.
Relay's commitMutation normally sends clientMutationId inside variables.input. Here it is a
sibling argument because gurafi input object names are compile-time constants. The response
shape matches Relay; a client that requires the field inside the input object is not
wire-compatible.
Security and limits
- Global IDs are the default; local IDs must be opted in.
- Unknown global type IDs in
nodesproduce a null item, not a null response. - Node loader errors are cached for the request, so a failing backend is not retried per field.
nodeswith onlyload_oneruns bounded concurrent loads.- Request-scoped caching is opt-in through
with_request_cache.
