moongql
A code-first GraphQL library for MoonBit: define object types and fields in code and print the schema SDL, then read SDL back into the same schema.
moon add moonbitstack/moongql✶ A schema in four lines
let s = Schema::new() let q = s.object("Query") q.field("hello", NonNull(Scalar("String"))) let sdl = s.to_sdl() // "schema { query: Query } type Query { hello: String! }"
§Schema & SDL
Code-first GraphQL: build object types and fields, then to_sdl emits the schema SDL. GqlType covers scalars, named refs, NonNull and List wrappers.
enum GqlType
A GraphQL type reference: a scalar (String, Int, Boolean, Float, ID), a named object type, or a non-null / list wrapper around another type.
fn type_sdl(t : GqlType) -> String
Render a type reference to GraphQL SDL notation (String!, [User!], ...).
struct Field
A field on an object type: a name, an ordered list of arguments (each a (name, type) pair), and the field's return type. A field with no arguments prints as name: Type; with arguments it prints as name(a: A, b: B): Type.
fn field_sdl(f : Field) -> String
Render a single field to SDL, including its argument list when non-empty, each argument's default value (= v), and its @deprecated(reason:). The description and the applied directives live on the schema, so they are the printer's job (see description_sdl and Schema::to_sdl), not this one's.
fn description_sdl(description : String?, indent~ : Int32 = 0) -> String
Render a description as the SDL string literal that precedes the element it describes, followed by a newline — or the empty string when there is none (an empty description is the same thing as no description, which is how introspection reports it too). indent is the element's own indentation, in levels of two spaces.
enum TypeKind
Which kind of composite type an ObjectType describes: an output object (type), an input object, or an interface.
struct ObjectType
A GraphQL composite type with an ordered set of fields. The same shape backs output objects, input objects, and interfaces; kind selects the SDL keyword and interfaces lists the interfaces an object implements.
fn ObjectType::field( self : ObjectType, name : String, typ : GqlType, deprecation_reason? : String, description? : String) -> Unit
Add a field with no arguments to this type.
fn ObjectType::field_args( self : ObjectType, name : String, args : Array[(String, GqlType)], typ : GqlType, defaults~ : Array[(String, Json)] = [], deprecation_reason? : String, description? : String) -> Unit
Add a field carrying arguments to this type. Each argument is a (name, type) pair and renders as name(a: A, b: B): RetType. defaults supplies default values for arguments a query may omit (GraphQL field-argument defaults): an absent argument takes its default, an explicitly-null one does not.
fn ObjectType::implements(self : ObjectType, name : String) -> Unit
Declare that this object type implements a named interface. Implemented interfaces render as type Name implements A & B { ... }.
struct EnumType
A GraphQL enum type: a name, an ordered list of value names, and each value's @deprecated reason keyed by value name (mirroring Field.arg_defaults).
struct UnionType
A GraphQL union type: a name and the ordered names of its member object types. A value at a union position is resolved to one member via its __typename, and only inline/named fragments on member types (plus __typename) may select into it.
struct ScalarType
A custom scalar type and its two coercion hooks: serialize maps a resolved value to its output JSON, and parse_value maps an input JSON value (an argument or a variable) to the value a resolver sees. Both default to identity when a scalar only renames an existing representation.
struct Schema
A code-first GraphQL schema: composite types (objects, inputs, interfaces), enum types, plus the names of the root operation types. query is required; mutation and subscription are optional (a schema without them cannot run operations of that kind).
fn Schema::new(query~ : String = "Query") -> Schema
Create an empty schema whose root query type is query (default Query), with no mutation or subscription root until set_mutation / set_subscription name them.
fn Schema::set_mutation(self : Schema, name : String) -> Unit
Name the root mutation type; it must also be declared with object.
fn Schema::set_subscription(self : Schema, name : String) -> Unit
Name the root subscription type; it must also be declared with object.
fn Schema::type_by_name(self : Schema, name : String) -> ObjectType?
Look up a declared composite type (object / input / interface) by name.
fn Schema::enum_by_name(self : Schema, name : String) -> EnumType?
Look up a declared enum type by name.
fn Schema::union( self : Schema, name : String, members : Array[String], description? : String) -> Unit
Declare a union type over the named member object types. Renders as union Name = A | B, preceded by description when one is given.
fn Schema::union_by_name(self : Schema, name : String) -> UnionType?
Look up a declared union type by name.
fn Schema::scalar( self : Schema, name : String, serialize~ : (Json) -> Json = fn(x)
Register a custom scalar with its serialize / parse_value hooks. Both default to identity, which is enough for a scalar that only renames JSON it already carries (a DateTime stored as an ISO string, say).
fn Schema::scalar_by_name(self : Schema, name : String) -> ScalarType?
Look up a registered custom scalar by name.
fn ObjectType::field_by_name(self : ObjectType, name : String) -> Field?
Look up a field on this type by its name.
fn GqlType::named_base(self : GqlType) -> String
The base named type a possibly-wrapped type refers to (unwrapping !/[]).
fn Schema::object( self : Schema, name : String, description? : String) -> ObjectType
Declare an output object type and return it so fields can be added. The type is registered by reference, so later .field(...) calls are seen.
fn Schema::input( self : Schema, name : String, description? : String) -> ObjectType
Declare an input object type and return it so fields can be added. Renders as input Name { ... }.
fn Schema::interface( self : Schema, name : String, description? : String) -> ObjectType
Declare an interface type and return it so fields can be added. Renders as interface Name { ... }; objects declare conformance via implements.
fn Schema::enum_( self : Schema, name : String, values : Array[String], deprecated~ : Array[(String, String)] = [], description? : String, descriptions~ : Array[(String, String)] = []) -> Unit
Declare an enum type with an ordered set of value names. Renders as enum Name { A B } with one value per line. deprecated marks individual values @deprecated, each a (value, reason) pair (as field_args takes argument defaults), surfacing in introspection's isDeprecated.
fn kind_keyword(k : TypeKind) -> String
The SDL keyword introducing a composite type of the given kind.
fn directive_def_sdl(d : DirectiveDef) -> String
Render a directive definition to SDL: directive @name(a: A) repeatable on LOCATION | LOCATION. A definition is what a Schema::directive call registers; the places a directive is *used* are AppliedDirectives, which applied_directive_sdl renders.
fn Schema::to_sdl(self : Schema) -> String
Emit the schema as GraphQL SDL: a schema { query: ... } block, then one block per directive definition, then one block per composite type (type / input / interface, with implements and field arguments), then one block per enum, union and scalar type. Every element carries its description, its @deprecated(reason:), its arguments' default values and its applied directives, so that Schema::of_sdl reading this text back yields the same schema (§3).
extend GqlType with Eq
extend TypeKind with Eq
§Query Lexer
Hand-written GraphQL lexer: turns a query string into names, punctuators, int/float/string tokens (block strings dedented per spec), ignoring whitespace, commas and comments. Pure logic, no core/lexbuf dependency.
suberror GqlSyntaxErrorA syntax error raised by the lexer or parser, carrying a message and the 1-based line/column where the offending token starts. It is pub(all) so that any stage reading GraphQL text — the SDL reader included — raises the same error, with the same shape, as the parser.
fn GqlSyntaxError::to_string(self : GqlSyntaxError) -> String
Render a syntax error as Syntax error at L:C: message.
impl Show for GqlSyntaxError with fn output(self, logger)
Prints the line and column the parse gave up at, which is the whole point of hand-writing the parser.
enum TokenKind
The lexical category of a token. Value-bearing kinds (Name, IntVal, FloatVal, StringVal, BlockStringVal) carry their text in Token::value.
struct Token
A lexical token: its kind, its text (for value-bearing kinds; unescaped for strings), and the 1-based line/column of its first character.
fn tokenize(src : String) -> Array[Token] raise GqlSyntaxError
Tokenize an entire source string into a token array ending with Eof. Primarily for tests and tooling; the parser pulls tokens on demand.
extend GqlSyntaxError with Show
extend TokenKind with Eq
§Document AST
The executable-document AST (operations, selection sets, fields, arguments, variables, fragments, directives, input values), with to_query printers that render each node back to canonical GraphQL.
struct Pos
The 1-based line and column of a node's first token. Every node an error can point back at carries one, so a validation or field error can answer with the locations entry the spec requires (§7.1.2).
struct Document
A parsed GraphQL document: an ordered list of executable definitions.
enum Definition
A top-level executable definition: an operation or a named fragment.
enum OperationType
Which kind of operation a definition describes.
struct OperationDefinition
An operation: its type, an optional name, variable definitions, directives and a selection set. The anonymous { ... } query shorthand parses to an OperationDefinition with operation = Query and name = None.
struct VariableDefinition
A variable declaration in an operation's (...) list: $name: Type = default with optional default value and directives.
enum TypeRef
A type reference as it appears in the query grammar: a named type, a list wrapper [T], or a non-null wrapper T!. Distinct from the schema builder's GqlType (which carries a Scalar sugar the parser never sees).
struct FragmentDefinition
A named fragment definition: fragment Name on Type @dir { selection }.
enum Selection
One entry in a selection set: a field, a ...Name fragment spread, or an inline ... on Type { ... } fragment.
struct QueryField
A queried field: an optional alias, the field name, arguments, directives and an optional nested selection set. Prints as alias: name(args) @dir { ... }.
struct Argument
A name: value argument on a field or directive.
struct Directive
A directive application: @name(args).
enum Value
A GraphQL input value. Numeric and string literals keep their source text (matching graphql-core, whose IntValueNode.value etc. are strings), so no lossy numeric round-trip is baked into the AST. StringValue's second field is the block-string flag.
fn escape_string(s : String) -> String
Escape a string's contents for printing inside GraphQL double quotes.
fn Value::to_query(self : Value) -> String
Print a value back to GraphQL literal syntax.
fn TypeRef::to_query(self : TypeRef) -> String
Print a type reference back to GraphQL notation ([Int!]!).
fn Document::to_query(self : Document) -> String
Print a whole document back to a canonical GraphQL string: each definition, separated by a blank line. Parsing this output yields an equivalent AST.
extend OperationType with Eq
extend Pos with Eq
extend TypeRef with Eq
§Query Parser
Recursive-descent parser: parse(source) builds a Document from operations (query/mutation/subscription and the { ... } shorthand), fields with aliases/arguments/directives, variable definitions, and named/inline fragments — the front half of the executor.
fn parse(source : String) -> Document raise GqlSyntaxError
Parse a GraphQL executable document from source text. This is the front half of the executor: parse(query).definitions yields the operations and fragments to validate and execute.
§Errors
GqlError, the error shape the specification's response section defines: a message, the path into the result and the source locations. Validation and execution both report in it, which is why it sits with the language rather than with either stage.
struct GqlError
A GraphQL error entry: a message, an optional response path (string field keys and integer list indices), and optional source locations.
fn GqlError::msg(message : String) -> GqlError
Build an error carrying only a message, for the request-level failures that cannot be pinned to a point in the document (an empty document, an unknown operation name).
fn GqlError::at(message : String, pos : Pos) -> GqlError
Build an error carrying a message and the position of the node it is about. §7.1.2 requires locations on every error that can name one.
fn GqlError::to_json(self : GqlError) -> Json
Serialise an error to the GraphQL JSON error shape, omitting an empty path or locations list.
§Fragments
collect_fragments gathers a document's fragment definitions by name; validation and execution both resolve spreads against it.
fn collect_fragments(doc : Document) -> Map[String, FragmentDefinition]
Build the document's fragment table by name.
§Built-in Types
What the specification fixes for every schema: the five built-in scalars, the meta-fields __typename, __schema and __type, and the introspection object types those meta-fields return.
fn is_builtin_scalar(name : String) -> Bool
The five built-in scalar type names, which are leaves with no declaration.
fn is_meta_field(name : String) -> Bool
Whether name is an introspection meta-field (__-prefixed), which the spec forbids as a subscription root field.
fn introspection_type_def(name : String) -> ObjectType?
The synthetic definition of an introspection type by name, or None. These drive leaf-vs-composite completion when executing an introspection query.
§Validator
Validate a Document against the Schema before execution: fields and arguments exist, leaf vs composite selection sets, fragment spreads and type conditions resolve, and referenced variables are defined. Errors are collected, not raised.
fn validate( schema : @schema.Schema, doc : @lang.Document) -> Array[@lang.GqlError]
Validate a whole document against schema, returning all errors found (an empty array means the document is valid and ready to execute).
§Validation Rules
The document-level slice of the GraphQL validation table (spec §5): operation-name uniqueness and lone anonymous operation, fragment-name uniqueness, no-unused-fragments, fragment-spread cycle detection, and the variable-use collector behind the all-variables-used rule.
§Directives
The directive extension points: AppliedDirective records a directive use on a schema element (federation's @key / @shareable / @inaccessible / @override live here); DirectiveDef declares a custom directive with valid locations, args, repeatability and an optional on_field execution hook that transforms a resolved field value.
struct AppliedDirective
A directive applied to a schema element: its name and its constant arguments as ordered (name, value) pairs. Build one with AppliedDirective::new.
fn AppliedDirective::new( name : String, args~ : Array[(String, Json)] = []) -> AppliedDirective
An applied directive with no arguments, or with the given constant arguments.
type FieldDirectiveHook = (Json, Map[String, Json]) -> Json
The run-time hook a user directive may carry: given the resolved field value and the directive's coerced arguments, return the transformed value. This is how a custom executable directive (@upper, @default(value:), ...) alters a field after its resolver has run.
struct DirectiveDef
A directive definition: its name, the locations it may appear at (the __DirectiveLocation enum values, e.g. "FIELD", "FIELD_DEFINITION"), its declared args, whether it is_repeatable, and an optional executable on_field hook. A definition with locations that include executable ones and an on_field hook is applied during execution; one with only type-system locations is a pure schema directive (SDL + introspection only).
fn Schema::directive( self : Schema, name : String, locations~ : Array[String], args~ : Array[(String, GqlType)] = [], is_repeatable~ : Bool = false, on_field? : FieldDirectiveHook) -> Unit
Register a directive definition on the schema. locations lists where it may be used ("FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT", "OBJECT", "FIELD_DEFINITION", ...); args declares its arguments; on_field — when given and the directive targets fields — transforms the resolved value of any field the directive is applied to. The directive then appears in introspection (__schema { directives }) and is accepted by the validator.
fn Schema::directive_def_by_name( self : Schema, name : String) -> DirectiveDef?
The registered directive definition named name, if any.
fn Schema::apply_field_directive( self : Schema, type_name : String, field_name : String, directive : AppliedDirective) -> Unit
Record an applied directive on the field type_name.field_name.
fn Schema::apply_type_directive( self : Schema, type_name : String, directive : AppliedDirective) -> Unit
Record an applied directive on the type type_name.
fn Schema::apply_schema_directive( self : Schema, directive : AppliedDirective) -> Unit
Record an applied directive on the schema itself (e.g. federation's @link).
fn Schema::field_applied_directives( self : Schema, type_name : String, field_name : String) -> Array[AppliedDirective]
The directives applied to the field type_name.field_name.
fn Schema::type_applied_directives( self : Schema, type_name : String) -> Array[AppliedDirective]
The directives applied to the type type_name.
fn Schema::field_has_directive( self : Schema, type_name : String, field_name : String, name : String) -> Bool
Whether the field type_name.field_name carries the directive name.
fn Schema::type_has_directive( self : Schema, type_name : String, name : String) -> Bool
Whether the type type_name carries the directive name.
fn json_to_gql_literal(j : Json) -> String
Render a JSON value as a GraphQL literal, for printing an applied directive's arguments into SDL (@override(from: "accounts"), @key(fields: "id name")).
fn applied_directive_sdl(d : AppliedDirective) -> String
Render one applied directive to SDL: @name or @name(a: v, b: w).
fn applied_directives_sdl(ds : Array[AppliedDirective]) -> String
Render a whole applied-directive list, each prefixed with a space (empty for an empty list), for appending after a field or type in SDL.
fn builtin_exec_directive(name : String) -> DirectiveDef?
The definition of a built-in executable directive (@skip / @include), or None for a name that is not a built-in. The validator reads its locations and its if: Boolean! argument; the executor evaluates the directive itself, so no on_field hook is attached.
§Executor
execute() runs a query end to end: parse, validate, then walk the document with a resolver map ((ResolveInfo) -> Json keyed by "Type.field"), supporting variables, aliases, fragments, @skip/@include, non-null error propagation and list/object results — returning { data, errors } as JSON.
struct ResolveInfo
The information a field resolver receives: the resolved parent object (as JSON), the coerced arguments, the shared context value, and the field's name. A resolver returns the field's value as JSON (an object for composite types, whose sub-fields are then resolved against it) and may raise ResolverError to surface a field error.
fn ResolveInfo::arg(self : ResolveInfo, name : String) -> Json
Read a named argument as JSON, or Json::null() when it was not supplied.
suberror ResolverErrorAn error raised by a field resolver; its message is reported in the response errors list with the field's response path.
struct Resolvers
A registry of field resolvers, keyed by "TypeName.fieldName". Fields with no registered resolver fall back to the default resolver, which reads the field's name off the parent JSON object (matching graphql-core's default_field_resolver).
fn Resolvers::new() -> Resolvers
Create an empty resolver registry.
fn Resolvers::field( self : Resolvers, type_name : String, field_name : String, resolver : (ResolveInfo) -> Json raise ResolverError) -> Unit
Register resolver for field field_name on type type_name.
fn build_response( data : Json?, errors : Array[@lang.GqlError], extensions~ : Map[String, Json] = Map([])) -> Json
Assemble the final { errors?, data?, extensions? } response object. data is omitted for request-level failures (no execution began) and included (possibly null) once execution starts; errors is omitted when empty, and so is the server's extensions map (§7.1, the entry reserved for whatever a server wants to say alongside the result — timings, a trace id, a cache hint).
fn execute( schema : @schema.Schema, resolvers : Resolvers, query : String, variables~ : Map[String, Json] = Map([]), operation_name? : String, root_value~ : Json = Json::null(), context~ : Json = Json::null(), extensions~ : Map[String, Json] = Map([])) -> Json
Execute a GraphQL request end to end: parse query, validate it against schema, select the operation, coerce variables, then walk the selection set with resolvers — returning the { data, errors } response as JSON. variables supplies operation variable values, operation_name picks an operation when the document has several, root_value seeds the root object, context is threaded to every resolver, and extensions is the server's own extensions entry on the response (§7.1), emitted when non-empty. The function never raises: parse and validation failures yield an errors-only response, and field errors are collected alongside partial data.
fn operation_type( schema : @schema.Schema, doc : @lang.Document, operation_name : String?) -> Result[@lang.OperationType, @lang.GqlError]
Which kind of operation a request would run, chosen the way execute chooses it. The server needs this before running anything: a subscription goes over a socket, and GraphQL over HTTP refuses a mutation sent by GET.
§Conversions
ToGql and FromGql carry a MoonBit value into a result and back out of an argument or a parent value; ResolveInfo::read reads an argument as any FromGql type, and field_error turns any error a resolver raised into a field error. The #gql generator writes both traits for each annotated type.
pub(open) trait ToGqlA MoonBit value as a GraphQL result: async-graphql's OutputType. Implemented for the built-in scalars, Int64 (written as a string, since GraphQL's Int is 32-bit), Json, T? and Array[T]; the #gql generator writes it for each annotated type.
pub(open) trait FromGqlA MoonBit value read from an argument or a parent value: async-graphql's InputType. A refusal is a ResolverError saying what was expected.
fn ResolverError::expected(want : String, got : Json) -> ResolverError
The refusal of a value that is not a want, as every [FromGql] here writes it: expected Int, got "x".
impl ToGql for Json with fn to_gql(self)
impl FromGql for Json with fn from_gql(v)
impl ToGql for String with fn to_gql(self)
impl FromGql for String with fn from_gql(v)
impl ToGql for Bool with fn to_gql(self)
impl FromGql for Bool with fn from_gql(v)
impl ToGql for Int32 with fn to_gql(self)
impl FromGql for Int32 with fn from_gql(v)
Refuses a fraction or a number outside 32 bits, as the spec's Int does.
impl ToGql for Double with fn to_gql(self)
impl FromGql for Double with fn from_gql(v)
impl ToGql for Int64 with fn to_gql(self)
impl FromGql for Int64 with fn from_gql(v)
Reads the string Int64 is written as, or a whole number.
impl[T : ToGql] ToGql for T? with fn to_gql(self)
impl[T : FromGql] FromGql for T? with fn from_gql(v)
impl[T : ToGql] ToGql for Array[T] with fn to_gql(self)
impl[T : FromGql] FromGql for Array[T] with fn from_gql(v)
fn[T : FromGql] ResolveInfo::read( self : ResolveInfo, name : String) -> T raise ResolverError
The argument name as any [FromGql] type; a missing argument reads as null, so it is None for a T?. A refusal names the argument.
fn[T : FromGql] read(value : Json, key : String) -> T raise ResolverError
The member key of an object value as any [FromGql] type, the way a generated input type reads its fields; a missing member reads as null. A refusal names the member.
fn field_error(e : Error) -> ResolverError
Any error a resolver raised, as the field error it becomes; a ResolverError passes through unchanged.
§Introspection
Answer __schema / __type / __typename from the schema: the introspection type system materialised as JSON that the executor walks like any object, with ofType wrapper chains, fields, interfaces, possibleTypes and enum values.
§Subscriptions
execute_subscription runs a single-root-field subscription against a source stream (a pull source returning Array[Json]) and returns the ordered { data, errors } payloads a client receives — the sync core the async WebSocket variant wraps.
struct Subscribers
A registry of subscription *source* resolvers, keyed by "TypeName.fieldName" on the subscription root type. A source returns the ordered stream of event payloads; each payload is the resolved value of the root field for one event, and its sub-selection is resolved against it like any object value.
fn Subscribers::new() -> Subscribers
Create an empty subscription source registry.
fn Subscribers::field( self : Subscribers, type_name : String, field_name : String, source : (ResolveInfo) -> Array[Json] raise ResolverError) -> Unit
Register a source stream for the root subscription field field_name on type_name. The source is called once per operation and yields the ordered events to deliver.
enum SubscriptionResult
The outcome of establishing a subscription (GraphQL spec §6.2.3 CreateSourceEventStream + MapSourceToResponseEvent): either the request failed before any event could be produced — a parse, validation, or source-resolution error the client should receive *instead of* a stream — or the source yielded its ordered { data, errors } response payloads. Keeping the two apart lets a transport (the graphql-transport-ws handler) send a single error message for the former and a run of next messages for the latter, which the flat "one error response in the payload list" shape cannot distinguish.
fn create_source_event_stream( schema : @schema.Schema, resolvers : Resolvers, subscribers : Subscribers, query : String, variables~ : Map[String, Json] = Map([]), operation_name? : String, root_value~ : Json = Json::null(), context~ : Json = Json::null()) -> SubscriptionResult
Establish a subscription and materialise its event payloads. Parses and validates query, requires a single-root-field subscription operation, invokes the registered source, and maps every event through execute_subscription_event. Any failure before the first event — parse/validation error, a non-subscription operation, more than one root field, an introspection root, a missing source, or a source ResolverError — is a RequestError; success is an EventStream of ordered response payloads.
fn execute_subscription( schema : @schema.Schema, resolvers : Resolvers, subscribers : Subscribers, query : String, variables~ : Map[String, Json] = Map([]), operation_name? : String, root_value~ : Json = Json::null(), context~ : Json = Json::null()) -> Array[Json]
Run a subscription operation and collect its ordered response payloads. A thin wrapper over create_source_event_stream: a RequestError collapses to a single { errors } response (the flat shape callers without a streaming transport expect), and an EventStream is returned as-is. variables, operation_name, root_value and context mirror execute.
§DataLoader
Batch-and-cache loader for the N+1 problem: keys requested in a resolution pass are queued and deduped, then one dispatch runs the batch function over the distinct keys and fills a per-loader cache. Includes prime, clear and load_many/load_now.
struct DataLoader[K, V]
A batching, caching loader over key type K and value type V. batch_load receives the distinct, un-cached keys and must return their values positionally (result i is the value for key i). key derives a stable cache/identity string for a key.
fn[K, V] DataLoader::new( batch_load : (Array[K]) -> Array[V], key : (K) -> String) -> DataLoader[K, V]
Create a loader from its batch-load function and a key-identity function.
fn[K, V] DataLoader::load(self : DataLoader[K, V], k : K) -> Unit
Queue k for the next dispatch. A key already cached or already queued in this pass is dropped, so the batch that dispatch runs sees each distinct key exactly once — this dedupe is what collapses N+1 into one call.
fn[K, V] DataLoader::load_many( self : DataLoader[K, V], ks : Array[K]) -> Unit
Queue several keys for the next dispatch.
fn[K, V] DataLoader::dispatch(self : DataLoader[K, V]) -> Unit
Run the pending batch: call batch_load once with the queued distinct keys, store each returned value in the cache under its key, and clear the queue. A no-op when nothing is queued, so it is safe to call after every pass.
fn[K, V] DataLoader::get(self : DataLoader[K, V], k : K) -> V?
The cached value for k, or None if it has not been loaded and dispatched.
fn[K, V] DataLoader::load_now(self : DataLoader[K, V], k : K) -> V?
Load one key and dispatch immediately, returning its value. Convenience for a single lookup; batching still applies to anything already queued.
fn[K, V] DataLoader::prime(self : DataLoader[K, V], k : K, v : V) -> Unit
Seed the cache with a known value so a later load for k never reaches the batch function.
fn[K, V] DataLoader::clear(self : DataLoader[K, V], k : K) -> Unit
Drop the cached value for k, so the next load+dispatch reloads it.
fn[K, V] DataLoader::clear_all(self : DataLoader[K, V]) -> Unit
Drop the whole cache.
§HTTP Endpoint
graphql_handler / graphql_app wire a schema onto the moonasgi seam: GET serves the GraphiQL IDE, POST reads a { query, variables, operationName } body and returns { data, errors }. Plugs into mooncat and runs on every backend through moonasgi's TestClient.
fn graphiql_html(endpoint~ : String = "/graphql") -> String
The GraphiQL IDE page, wired to fetch against endpoint. This is what site (the default Ide) serves; the pages are built in ide.mbt.
fn graphql_handler( schema : @schema.Schema, resolvers : @exec.Resolvers, path~ : String = "/graphql", ide~ : Ide = site) -> @moonasgi.Handler
Build the moonasgi request handler for a schema and its resolvers, following the GraphQL-over-HTTP specification. A POST on path executes an application/json body — one request object, or an array of them for a batch. A GET with a ?query= executes a query operation (only a query: a mutation there is a 405); a GET without one serves the IDE ide names (the MoonQL page by default, or off for a 405). The response media type is negotiated from Accept: a client asking for application/graphql-response+json gets it, and with it the status codes that separate a request error (400) from a field error (200); anything else gets application/json and a 200. Lift it onto an AsgiApp with graphql_app, or drive it directly with moonasgi's TestClient.
fn graphql_app( schema : @schema.Schema, resolvers : @exec.Resolvers, path~ : String = "/graphql", ide~ : Ide = site) -> @moonasgi.AsgiApp
Lift the GraphQL handler onto the load-bearing AsgiApp a server binds to. This is what mooncat (or any moonasgi server) mounts to serve the schema over real HTTP; the request→response logic is shared with graphql_handler, which TestClient exercises without a socket.
§Apollo Federation
Federation v1 and v2 subgraph support: register @key entities and their reference resolvers, then apply installs _service { sdl }, the _Any scalar, the _Entity union and the _entities(representations) resolver. v2 opts in with @link and adds @shareable / @inaccessible / @override / @requires / @provides on fields, rendered into the subgraph SDL.
type EntityResolver = (Json, Json) -> Json raise @exec.ResolverError
The reference resolver for an entity type: given a representation (a JSON object carrying __typename and the entity's key fields) and the request context, return the fully resolved entity as JSON. This is the subgraph's answer to "you have a key, give me the object" — Apollo's __resolveReference.
struct EntityDef
One federated entity: its type name, the key field set (@key(fields:)), whether the subgraph only extends a type it does not own, the names of its external fields (@external, owned by another subgraph), and the reference resolver that materialises it from a representation.
struct Federation
The federation configuration for a subgraph: the set of entity types it contributes, whether it is a Federation **v2** subgraph (which opts in with an @link to the federation spec and unlocks @shareable / @inaccessible / @override), and the field-level federation directives it declares. Build it with new, register entities with entity, mark fields with shareable / inaccessible / override_ / requires / provides, then apply it to a schema and resolver map.
fn Federation::new(v2~ : Bool = false) -> Federation
An empty federation config. Pass v2=true for a Federation v2 subgraph, whose SDL opens with extend schema @link(...) importing the federation spec and which may use the v2-only directives (@shareable, @inaccessible, @override).
fn Federation::shareable( self : Federation, type_name : String, field_name : String) -> Unit
Mark type_name.field_name @shareable — resolvable by more than one subgraph (v2). Without it, a non-key field must be owned by exactly one subgraph.
fn Federation::inaccessible( self : Federation, type_name : String, field_name : String) -> Unit
Mark type_name.field_name @inaccessible (v2) — present in this subgraph but omitted from the composed public schema, and hidden from introspection.
fn Federation::override_( self : Federation, type_name : String, field_name : String, from~ : String) -> Unit
Declare that type_name.field_name is @override-taken from the subgraph from (v2) — this subgraph now resolves the field the other used to own.
fn Federation::requires( self : Federation, type_name : String, field_name : String, fields~ : String) -> Unit
Declare that resolving type_name.field_name @requires the named external key fields ("weight size"), which the gateway then includes in the entity representation the _entities resolver receives.
fn Federation::provides( self : Federation, type_name : String, field_name : String, fields~ : String) -> Unit
Declare that resolving type_name.field_name @provides the named fields of the returned entity, so the gateway can skip a round trip for them.
fn Federation::entity( self : Federation, name~ : String, key~ : String, resolve~ : EntityResolver, extends~ : Bool = false, external~ : Array[String] = []) -> Unit
Register an entity type. name is the object type (which must also be declared on the schema), key is its @key field set ("id", or space- separated "upc sku" for a compound key), resolve turns a representation back into the object. Set extends when this subgraph extends a type owned by another, and list external fields that other subgraphs own.
fn Federation::sdl(self : Federation, schema : @schema.Schema) -> String
Render the subgraph SDL: the schema as the developer wrote it, annotated with @key on entity types (prefixed extend when the type is an extension), @external on external fields, and with the federation-internal additions (_Service, _Entity, _Any, and the _service / _entities root fields) left out. This is what _service { sdl } returns.
fn Federation::apply( self : Federation, schema : @schema.Schema, resolvers : @exec.Resolvers) -> Unit
Install the federation machinery onto schema and resolvers: declare the _Any scalar, the _Service type with its sdl field, and the _Entity union over every registered entity type; add the _service and _entities fields to the query root; and register the two resolvers. After this the schema answers a federated query — { _service { sdl } } and _entities(representations:) — through the normal executor.
§Built-in IDE
The IDE the endpoint serves on a GET without a query: GraphiQL, Apollo Sandbox or Altair, chosen with one value. Each page is built here with its assets pinned to an exact version, and the subscription socket is derived from the page's own location unless you name one.
enum IdeKind
Which IDE the endpoint serves.
struct Ide
The IDE a GET without a ?query= is answered with. subscription_endpoint is where the IDE opens its WebSocket: left as None, each page derives one from the page's own location (the same host, wss: over HTTPS), which is right for an endpoint served next to the IDE.
let site : Ide
The MoonQL page: the library's own IDE, and the default.
let graphiql : Ide
GraphiQL, the reference IDE.
let apollo_sandbox : Ide
Apollo Sandbox, embedded as an iframe.
let altair : Ide
Altair, the IDE with environments and saved collections.
let off : Ide
No IDE at all: a GET without a ?query= is a 405.
fn Ide::is_off(self : Ide) -> Bool
Whether this configuration serves no IDE.
fn Ide::title(self : Ide) -> String
The page title the IDE's HTML carries.
fn Ide::html(self : Ide, endpoint~ : String = "/graphql") -> String
The full HTML page mounting this IDE against endpoint. Off has no page, and answers the empty string.
extend IdeKind with Eq
§Reading SDL
The other direction: build a Schema from SDL text. print-then-parse is a round trip, so a schema written as SDL and the same schema built in code print identically; a definition that stops mid-way reports the line and column where it stopped.
fn Schema::of_sdl(text : String) -> Schema raise @lang.GqlSyntaxError
Build a schema from a type-system document (SDL), the inverse of to_sdl. A document that is not a well-formed type-system definition raises GqlSyntaxError carrying the line and column of the token that stopped it. The hooks SDL cannot carry come back as the identity: a custom scalar's serialize / parse_value, and a directive definition's on_field. ``mbt nocheck let s = Schema::of_sdl( ( #|schema { query: Query } #| #|"The root of everything." #|type Query { #| "Greets you." #| hello(name: String = "world"): String! #|} ), ) // s.type_by_name("Query") carries the description and the default value. ``
§graphql-transport-ws
The WebSocket subprotocol a subscription client speaks: connection_init and its ack, ping/pong, subscribe with single-use operation ids, and the next / error / complete messages a running operation produces.
struct GqlWs
One graphql-transport-ws connection: the schema it serves plus the protocol state a connection carries — whether connection_init has arrived, the parameters it brought, the operation ids currently running, and the two timers the protocol defines. This package has no async runtime and no clock of its own, so the timers are state plus a transition and nothing more: a server drives them by calling tick with a millisecond reading of a monotonic clock, as often as it likes (once a second is plenty), and sending whatever frames come back. Nothing else starts the connection-init countdown or emits a keep-alive ping.
fn GqlWs::new( schema : @schema.Schema, resolvers : @exec.Resolvers, subscribers : @exec.Subscribers, root_value~ : Json = Json::null(), context~ : Json = Json::null(), init_timeout~ : Int64 = 3000, ping_interval~ : Int64 = 12000) -> GqlWs
Open a connection's protocol state for schema / resolvers / subscribers. root_value and context are threaded to every operation the way execute threads them. init_timeout is how long a client may take to send connection_init before tick closes the socket with 4408, and ping_interval how long the server waits between keep-alive ping frames; both are milliseconds, and either is switched off by passing zero.
fn GqlWs::recv( self : GqlWs, msg : @moonasgi.WsMessage) -> Array[@moonasgi.WsSend]
Handle one inbound message, returning the frames to send back.
fn GqlWs::tick(self : GqlWs, now : Int64) -> Array[@moonasgi.WsSend]
Drive the connection's timers from a monotonic clock reading in milliseconds, returning the frames the elapsed time calls for: the 4408 close when connection_init did not arrive in time, or a keep-alive ping. The first call starts the clock, so a server should tick as soon as it has accepted the socket and keep ticking for as long as it holds it.
fn GqlWs::claim(self : GqlWs, id : String) -> Bool
Claim operation id for an operation that is starting, answering false when the id is already running — the caller must then close the socket with 4409. recv claims and releases an id itself, because a pull source is drained before it returns. A server streaming a live source claims the id when it starts the task and calls release when the task sends its last frame; that is what keeps 4409 meaning "still running" rather than "used once".
fn GqlWs::release(self : GqlWs, id : String) -> Unit
Release operation id, freeing it for reuse. Called after the operation's terminating complete or error has been sent.
fn GqlWs::handler(self : GqlWs) -> @moonasgi.WebSocketHandler
The connection as a moonasgi WebSocketHandler, negotiating the graphql-transport-ws subprotocol when the client offers it.
fn graphql_ws_handler( schema : @schema.Schema, resolvers : @exec.Resolvers, subscribers : @exec.Subscribers, root_value~ : Json = Json::null(), context~ : Json = Json::null()) -> @moonasgi.WebSocketHandler
Build a graphql-transport-ws WebSocket handler for schema / resolvers / subscribers. root_value and context are threaded to every operation the way execute threads them. Negotiates the graphql-transport-ws subprotocol when the client offers it. The handler owns its connection state, so nothing can drive the protocol's timers: build a GqlWs and take its handler when the server can tick.