mochi/schema

Core schema types and execution context for GraphQL.

This module provides:

ExecutionContext with DataLoaders

// Create context with multiple loaders
let ctx = schema.execution_context(types.to_dynamic(dict.new()))
  |> schema.with_loaders([
    #("pokemon", pokemon_loader),
    #("trainer", trainer_loader),
  ])

// Load by ID (handles context threading automatically)
let #(ctx, pokemon) = schema.load_by_id(ctx, "pokemon", 25)
let #(ctx, trainers) = schema.load_many_by_id(ctx, "trainer", [1, 2, 3])

Schema Building

let my_schema = schema.schema()
  |> schema.query(query_type)
  |> schema.mutation(mutation_type)
  |> schema.add_type(schema.ObjectTypeDef(user_type))
  |> schema.add_directive(auth_directive)

Types

pub type ArgumentDefinition {
  ArgumentDefinition(
    name: String,
    description: option.Option(String),
    arg_type: FieldType,
    default_value: option.Option(dynamic.Dynamic),
  )
}

Constructors

Which storage columns a field needs, so query.selected_columns can derive a projection from the schema instead of a hand-written mapping.

The default is DerivedColumn, never “no columns”: a field nobody declared still asks for something, so a wrong guess surfaces as a database error rather than as a silently blank row.

pub type ColumnSource {
  DerivedColumn
  Columns(names: List(String))
  NoColumns
}

Constructors

  • DerivedColumn

    Undeclared: the column is the field name in snake_case (fullName -> full_name).

  • Columns(names: List(String))

    The columns this field needs. One field may need several (fullName -> first_name, last_name).

  • NoColumns

    This field needs no column of its own — a join, or a value computed from columns that other fields already claim.

Custom directive definition

pub type DirectiveDefinition {
  DirectiveDefinition(
    name: String,
    description: option.Option(String),
    arguments: dict.Dict(String, ArgumentDefinition),
    locations: List(DirectiveLocation),
    is_repeatable: Bool,
    handler: option.Option(
      fn(dict.Dict(String, dynamic.Dynamic), dynamic.Dynamic) -> Result(
        dynamic.Dynamic,
        String,
      ),
    ),
  )
}

Constructors

Handler function for custom directives Takes the directive arguments and field value, returns modified value or error

pub type DirectiveHandler =
  fn(dict.Dict(String, dynamic.Dynamic), dynamic.Dynamic) -> Result(
    dynamic.Dynamic,
    String,
  )

Locations where a directive can be used

pub type DirectiveLocation {
  QueryLocation
  MutationLocation
  SubscriptionLocation
  FieldLocation
  FragmentDefinitionLocation
  FragmentSpreadLocation
  InlineFragmentLocation
  VariableDefinitionLocation
  SchemaLocation
  ScalarLocation
  ObjectLocation
  FieldDefinitionLocation
  ArgumentDefinitionLocation
  InterfaceLocation
  UnionLocation
  EnumLocation
  EnumValueLocation
  InputObjectLocation
  InputFieldDefinitionLocation
}

Constructors

  • QueryLocation
  • MutationLocation
  • SubscriptionLocation
  • FieldLocation
  • FragmentDefinitionLocation
  • FragmentSpreadLocation
  • InlineFragmentLocation
  • VariableDefinitionLocation
  • SchemaLocation
  • ScalarLocation
  • ObjectLocation
  • FieldDefinitionLocation
  • ArgumentDefinitionLocation
  • InterfaceLocation
  • UnionLocation
  • EnumLocation
  • EnumValueLocation
  • InputObjectLocation
  • InputFieldDefinitionLocation
pub type EnumType {
  EnumType(
    name: String,
    description: option.Option(String),
    values: dict.Dict(String, EnumValueDefinition),
  )
}

Constructors

pub type EnumValueDefinition {
  EnumValueDefinition(
    name: String,
    description: option.Option(String),
    value: dynamic.Dynamic,
    is_deprecated: Bool,
    deprecation_reason: option.Option(String),
  )
}

Constructors

Context for GraphQL execution, including DataLoader instances

pub type ExecutionContext {
  ExecutionContext(
    user_context: UserContext,
    data_loaders: dict.Dict(
      String,
      dataloader.DataLoader(dynamic.Dynamic, dynamic.Dynamic),
    ),
    middleware_fn: option.Option(
      fn(
        String,
        FieldDefinition,
        ResolverInfo,
        fn(ResolverInfo) -> Result(dynamic.Dynamic, String),
      ) -> Result(dynamic.Dynamic, String),
    ),
    telemetry: option.Option(TelemetryContext),
    telemetry_fn: option.Option(fn(SchemaEvent) -> Nil),
    selection: Selection,
  )
}

Constructors

  • ExecutionContext(
      user_context: UserContext,
      data_loaders: dict.Dict(
        String,
        dataloader.DataLoader(dynamic.Dynamic, dynamic.Dynamic),
      ),
      middleware_fn: option.Option(
        fn(
          String,
          FieldDefinition,
          ResolverInfo,
          fn(ResolverInfo) -> Result(dynamic.Dynamic, String),
        ) -> Result(dynamic.Dynamic, String),
      ),
      telemetry: option.Option(TelemetryContext),
      telemetry_fn: option.Option(fn(SchemaEvent) -> Nil),
      selection: Selection,
    )

    Arguments

    user_context

    Custom user context data. Construct via schema.user_context(value), read via schema.context_accessor or schema.read_user_context.

    data_loaders

    DataLoader instances for batching and caching

    middleware_fn

    Optional middleware function for field resolution

    telemetry

    Optional telemetry context for instrumentation (opaque, legacy)

    telemetry_fn

    Optional telemetry event callback — receives SchemaEvents during execution. Use telemetry.to_schema_fn/1 to bridge to a full TelemetryConfig.

    selection

    What the client selected on the value this resolver returns. Set by the executor before each resolver call. Computed on demand, so a resolver that never asks pays nothing — read it via selected_fields, has_selection, selection_of, or selection for the whole list.

pub type FieldDefinition {
  FieldDefinition(
    name: String,
    description: option.Option(String),
    field_type: FieldType,
    arguments: dict.Dict(String, ArgumentDefinition),
    resolver: option.Option(
      fn(ResolverInfo) -> Result(dynamic.Dynamic, String),
    ),
    is_deprecated: Bool,
    deprecation_reason: option.Option(String),
    topic_fn: option.Option(
      fn(args.Args, ExecutionContext) -> Result(String, String),
    ),
    rich_resolver: option.Option(
      fn(ResolverInfo) -> Result(
        dynamic.Dynamic,
        #(
          String,
          option.Option(dict.Dict(String, dynamic.Dynamic)),
        ),
      ),
    ),
    columns: ColumnSource,
  )
}

Constructors

A guard function that runs before the resolver. Returns Ok(Nil) to allow the resolver to proceed, or Error(message) to reject.

pub type FieldGuard =
  fn(ResolverInfo) -> Result(Nil, String)
pub type FieldType {
  NonNull(inner: FieldType)
  List(inner: FieldType)
  Named(name: String)
}

Constructors

pub type InputFieldDefinition {
  InputFieldDefinition(
    name: String,
    description: option.Option(String),
    field_type: FieldType,
    default_value: option.Option(dynamic.Dynamic),
  )
}

Constructors

pub type InputObjectType {
  InputObjectType(
    name: String,
    description: option.Option(String),
    fields: dict.Dict(String, InputFieldDefinition),
  )
}

Constructors

pub type InterfaceType {
  InterfaceType(
    name: String,
    description: option.Option(String),
    fields: dict.Dict(String, FieldDefinition),
    resolve_type: option.Option(
      fn(dynamic.Dynamic) -> Result(String, String),
    ),
  )
}

Constructors

Function type for middleware execution - avoids circular dependency with middleware.gleam

pub type MiddlewareFn =
  fn(
    String,
    FieldDefinition,
    ResolverInfo,
    fn(ResolverInfo) -> Result(dynamic.Dynamic, String),
  ) -> Result(dynamic.Dynamic, String)
pub type ObjectType {
  ObjectType(
    name: String,
    description: option.Option(String),
    fields: dict.Dict(String, FieldDefinition),
    interfaces: List(InterfaceType),
  )
}

Constructors

pub type Resolver =
  fn(ResolverInfo) -> Result(dynamic.Dynamic, String)

Information passed to field resolvers.

args is the typed view; arguments is the legacy raw dict. New resolvers should read via args.get_string(info.args, "id") etc. arguments is kept so existing resolvers keep compiling, and so callers that need to forward the raw dict can.

pub type ResolverInfo {
  ResolverInfo(
    parent: option.Option(dynamic.Dynamic),
    arguments: dict.Dict(String, dynamic.Dynamic),
    args: args.Args,
    context: ExecutionContext,
    info: dynamic.Dynamic,
  )
}

Constructors

pub type RichResolver =
  fn(ResolverInfo) -> Result(
    dynamic.Dynamic,
    #(String, option.Option(dict.Dict(String, dynamic.Dynamic))),
  )
pub type RichResolverPayload =
  #(String, option.Option(dict.Dict(String, dynamic.Dynamic)))
pub type ScalarType {
  ScalarType(
    name: String,
    description: option.Option(String),
    serialize: fn(dynamic.Dynamic) -> Result(
      dynamic.Dynamic,
      String,
    ),
    parse_value: fn(dynamic.Dynamic) -> Result(
      dynamic.Dynamic,
      String,
    ),
    parse_literal: fn(dynamic.Dynamic) -> Result(
      dynamic.Dynamic,
      String,
    ),
  )
}

Constructors

pub type Schema {
  Schema(
    query: option.Option(ObjectType),
    mutation: option.Option(ObjectType),
    subscription: option.Option(ObjectType),
    types: dict.Dict(String, TypeDefinition),
    directives: dict.Dict(String, DirectiveDefinition),
    document_cache: option.Option(document_cache.DocumentCache),
  )
}

Constructors

Events emitted during GraphQL execution for telemetry/instrumentation. These are emitted by the executor and passed to the telemetry_fn if one is configured.

pub type SchemaEvent {
  SchemaParseStart
  SchemaParseEnd(success: Bool, duration_ns: Int)
  SchemaValidationStart
  SchemaValidationEnd(
    success: Bool,
    error_count: Int,
    duration_ns: Int,
  )
  SchemaFieldStart(
    field_name: String,
    parent_type: String,
    path: List(String),
  )
  SchemaFieldEnd(
    field_name: String,
    parent_type: String,
    path: List(String),
    success: Bool,
    duration_ns: Int,
  )
  SchemaOperationStart(operation_name: option.Option(String))
  SchemaOperationEnd(
    operation_name: option.Option(String),
    success: Bool,
    error_count: Int,
  )
  SchemaDataLoaderBatch(
    loader_name: String,
    batch_size: Int,
    duration_ns: Int,
  )
}

Constructors

  • SchemaParseStart

    Query parsing is about to start.

  • SchemaParseEnd(success: Bool, duration_ns: Int)

    Query parsing has completed.

  • SchemaValidationStart

    Query validation is about to start.

  • SchemaValidationEnd(
      success: Bool,
      error_count: Int,
      duration_ns: Int,
    )

    Query validation has completed.

  • SchemaFieldStart(
      field_name: String,
      parent_type: String,
      path: List(String),
    )

    A field resolver is about to be called.

  • SchemaFieldEnd(
      field_name: String,
      parent_type: String,
      path: List(String),
      success: Bool,
      duration_ns: Int,
    )

    A field resolver has completed.

  • SchemaOperationStart(operation_name: option.Option(String))

    A GraphQL operation is about to be executed.

  • SchemaOperationEnd(
      operation_name: option.Option(String),
      success: Bool,
      error_count: Int,
    )

    A GraphQL operation has completed.

  • SchemaDataLoaderBatch(
      loader_name: String,
      batch_size: Int,
      duration_ns: Int,
    )

    DataLoader batch was executed.

A field the client asked for, plus its own sub-selection.

name is the schema field name, never the response alias, so it maps straight onto a column allowlist. Meta fields (__typename, __schema, __type) are excluded, and @skip/@include are already applied. Entries are merged by name, so the same field selected twice with different arguments appears once with the union of its sub-selections.

pub type SelectedField {
  SelectedField(
    name: String,
    only_for: option.Option(List(String)),
    arguments: dict.Dict(String, dynamic.Dynamic),
    columns: List(String),
    children: List(SelectedField),
  )
}

Constructors

  • SelectedField(
      name: String,
      only_for: option.Option(List(String)),
      arguments: dict.Dict(String, dynamic.Dynamic),
      columns: List(String),
      children: List(SelectedField),
    )

    Arguments

    only_for

    Concrete object type names this field applies to, or None when it was selected unconditionally. Set when the field appeared inside a fragment carrying a type condition; interfaces and unions are expanded to their concrete members, so a resolver returning one of several types can ask for exactly its own fields via selected_fields_for.

    arguments

    Arguments written on this field, with variables substituted. Raw literal shapes — not coerced against the schema, so schema defaults are absent and enum values arrive as strings.

    columns

    Storage columns this field needs, taken from the schema — declared with types.from_columns / types.no_columns, or the field name in snake_case when undeclared. query.selected_columns sums these, so no hand-written field-to-column mapping is needed.

The selection carried by an ExecutionContext, computed on first read.

Building it walks the query’s selection set, so it is deferred: resolvers that ignore the selection never pay for it. Each read recomputes, so bind it once (let selected = schema.selection(ctx)) when using it repeatedly.

pub opaque type Selection

Opaque type for telemetry context (defined in mochi/telemetry.gleam)

pub type TelemetryContext

Callback for receiving schema execution events. Wire this up to a telemetry handler (e.g. via telemetry.to_schema_fn/1).

pub type TelemetryFn =
  fn(SchemaEvent) -> Nil
pub type TypeDefinition {
  ObjectTypeDef(object_type: ObjectType)
  ScalarTypeDef(scalar_type: ScalarType)
  EnumTypeDef(enum_type: EnumType)
  InterfaceTypeDef(interface_type: InterfaceType)
  UnionTypeDef(union_type: UnionType)
  InputObjectTypeDef(input_object_type: InputObjectType)
}

Constructors

Type resolver function that determines the concrete type at runtime

pub type TypeResolver =
  fn(dynamic.Dynamic) -> Result(String, String)
pub type UnionType {
  UnionType(
    name: String,
    description: option.Option(String),
    types: List(ObjectType),
    resolve_type: option.Option(
      fn(dynamic.Dynamic) -> Result(String, String),
    ),
  )
}

Constructors

Opaque holder for user-provided execution context data.

Resolvers expect a specific user context type — the one their app configured. Exposing the raw Dynamic in ExecutionContext lied about that: it advertised “any data here” when in fact the system requires the app’s chosen type. UserContext keeps the runtime erasure (necessary because the executor doesn’t know the app’s type) but stops the lie at the type level.

Use user_context to wrap a value, and either context_accessor for typed reads, or read_user_context for one-off reads.

pub opaque type UserContext

Values

pub fn add_directive(
  schema: Schema,
  directive: DirectiveDefinition,
) -> Schema

Add a directive definition to the schema

pub fn add_type(
  schema: Schema,
  type_def: TypeDefinition,
) -> Schema
pub fn all_guards(
  guard_fns: List(fn(ResolverInfo) -> Result(Nil, String)),
) -> fn(ResolverInfo) -> Result(Nil, String)

Combine guards with AND logic — all must pass (checked in list order). Returns a single guard that fails with the first error encountered.

pub fn any_guard(
  guard_fns: List(fn(ResolverInfo) -> Result(Nil, String)),
) -> fn(ResolverInfo) -> Result(Nil, String)

Combine guards with OR logic — at least one must pass. Returns a single guard that succeeds if any guard passes, or fails with the last error if all fail.

pub fn arg(
  name: String,
  arg_type: FieldType,
) -> ArgumentDefinition
pub fn arg_description(
  arg: ArgumentDefinition,
  desc: String,
) -> ArgumentDefinition
pub fn argument(
  field: FieldDefinition,
  arg_def: ArgumentDefinition,
) -> FieldDefinition
pub fn auto_field(
  obj: ObjectType,
  name: String,
  field_type: FieldType,
) -> ObjectType

Add a field with auto-resolver (extracts field by name from parent)

pub fn auto_resolver(
  field_name: String,
) -> fn(ResolverInfo) -> Result(dynamic.Dynamic, String)

Create a resolver that auto-extracts a field from parent by name

pub fn bool_field(obj: ObjectType, name: String) -> ObjectType

Add a nullable Boolean field with auto-resolver

pub fn boolean_scalar() -> ScalarType
pub fn boolean_type() -> FieldType
pub fn builtin_directives() -> List(DirectiveDefinition)

Get all built-in directives

pub fn context_accessor(
  decoder: decode.Decoder(a),
) -> fn(ExecutionContext) -> Result(a, String)

Create a typed accessor for user_context. Define once per app, use in every resolver.

// context.gleam
pub const get_user_id = schema.context_accessor(decode.optional(decode.string))

// resolver
let auth = ctx |> context.get_user_id |> require_auth
use uid <- result.try(auth)
pub fn date_type() -> FieldType
pub fn datetime_type() -> FieldType
pub fn deprecated(
  field: FieldDefinition,
  reason: String,
) -> FieldDefinition
pub fn deprecated_directive() -> DirectiveDefinition

@deprecated(reason: String) directive

pub fn deprecated_no_reason(
  field: FieldDefinition,
) -> FieldDefinition
pub fn description(obj: ObjectType, desc: String) -> ObjectType
pub fn directive(
  name: String,
  locations: List(DirectiveLocation),
) -> DirectiveDefinition

Create a new directive definition

pub fn directive_argument(
  dir: DirectiveDefinition,
  arg_def: ArgumentDefinition,
) -> DirectiveDefinition

Add an argument to a directive

pub fn directive_description(
  dir: DirectiveDefinition,
  desc: String,
) -> DirectiveDefinition

Add description to a directive

pub fn directive_handler(
  dir: DirectiveDefinition,
  handler: fn(dict.Dict(String, dynamic.Dynamic), dynamic.Dynamic) -> Result(
    dynamic.Dynamic,
    String,
  ),
) -> DirectiveDefinition

Set the handler function for a directive (for field-level directives)

pub fn directive_location_to_string(
  loc: DirectiveLocation,
) -> String

Convert DirectiveLocation to string (for SDL generation)

pub fn directive_repeatable(
  dir: DirectiveDefinition,
) -> DirectiveDefinition

Make a directive repeatable

pub fn email_type() -> FieldType
pub fn execution_context(
  user_context user_context_value: a,
) -> ExecutionContext

Create a new execution context. The user_context value can be any app-defined type; it is wrapped opaquely and surfaced to resolvers via context_accessor / read_user_context.

pub fn field(
  obj: ObjectType,
  field_def: FieldDefinition,
) -> ObjectType
pub fn field_columns(
  field: FieldDefinition,
  names: List(String),
) -> FieldDefinition

Declare the columns a field needs.

pub fn field_def(
  name: String,
  field_type: FieldType,
) -> FieldDefinition
pub fn field_description(
  field: FieldDefinition,
  desc: String,
) -> FieldDefinition
pub fn field_no_columns(
  field: FieldDefinition,
) -> FieldDefinition

Declare that a field needs no column of its own.

pub fn float_field(obj: ObjectType, name: String) -> ObjectType

Add a nullable Float field with auto-resolver

pub fn float_scalar() -> ScalarType
pub fn float_type() -> FieldType
pub fn get_data_loader(
  context: ExecutionContext,
  name: String,
) -> Result(
  dataloader.DataLoader(dynamic.Dynamic, dynamic.Dynamic),
  String,
)

Get a DataLoader from the execution context

pub fn get_middleware(
  context: ExecutionContext,
) -> option.Option(
  fn(
    String,
    FieldDefinition,
    ResolverInfo,
    fn(ResolverInfo) -> Result(dynamic.Dynamic, String),
  ) -> Result(dynamic.Dynamic, String),
)

Get the middleware function from context

pub fn get_telemetry(
  context: ExecutionContext,
) -> option.Option(TelemetryContext)

Get the telemetry context

pub fn get_telemetry_fn(
  context: ExecutionContext,
) -> option.Option(fn(SchemaEvent) -> Nil)

Get the telemetry callback function from the execution context

pub fn guard(
  field: FieldDefinition,
  guard_fn: fn(ResolverInfo) -> Result(Nil, String),
) -> FieldDefinition

Add a guard to a field definition. The guard runs before the resolver — if it returns Error, the resolver is skipped. Multiple guards can be stacked by calling this function multiple times; each new guard wraps the previous resolver+guards, so all must pass.

pub fn guards(
  field: FieldDefinition,
  guard_fns: List(fn(ResolverInfo) -> Result(Nil, String)),
) -> FieldDefinition

Add multiple guards to a field definition. Guards are checked in list order — the first guard in the list is checked first.

schema.field_def("secret", schema.string_type())
  |> schema.resolver(my_resolver)
  |> schema.guards([require_auth, require_admin])
  // require_auth is checked first, then require_admin
pub fn id_field(obj: ObjectType, name: String) -> ObjectType

Add a non-null ID field with auto-resolver

pub fn id_scalar() -> ScalarType
pub fn id_type() -> FieldType
pub fn implements(
  obj: ObjectType,
  interface: InterfaceType,
) -> ObjectType
pub fn include_directive() -> DirectiveDefinition

@include(if: Boolean!) directive

pub fn int_field(obj: ObjectType, name: String) -> ObjectType

Add a nullable Int field with auto-resolver

pub fn int_scalar() -> ScalarType
pub fn int_type() -> FieldType
pub fn interface(name: String) -> InterfaceType
pub fn interface_description(
  iface: InterfaceType,
  desc: String,
) -> InterfaceType
pub fn interface_field(
  iface: InterfaceType,
  field_def: FieldDefinition,
) -> InterfaceType
pub fn interface_resolve_type(
  iface: InterfaceType,
  resolver: fn(dynamic.Dynamic) -> Result(String, String),
) -> InterfaceType
pub fn json_type() -> FieldType
pub fn list_field(
  obj: ObjectType,
  name: String,
  item_type: String,
) -> ObjectType

Add a list field with auto-resolver

pub fn list_query(
  obj: ObjectType,
  name: String,
  item_type: String,
  desc: String,
  resolve_fn: fn(ResolverInfo) -> Result(dynamic.Dynamic, String),
) -> ObjectType

Add a list query field: name: [ItemType]

pub fn list_type(inner: FieldType) -> FieldType
pub fn load(
  context: ExecutionContext,
  loader_name: String,
  key: dynamic.Dynamic,
) -> #(ExecutionContext, Result(dynamic.Dynamic, String))

Load a value using a named DataLoader, returning updated context and result

This handles the context threading automatically.

Example

let #(ctx, result) = schema.load(ctx, "pokemon", dataloader.int_key(25))
pub fn load_by_id(
  context: ExecutionContext,
  loader_name: String,
  id: Int,
) -> #(ExecutionContext, Result(dynamic.Dynamic, String))

Load an entity by Int ID using a named DataLoader

Convenience wrapper for the common case of loading by integer ID.

Example

let #(ctx, pokemon) = schema.load_by_id(ctx, "pokemon", 25)
pub fn load_many(
  context: ExecutionContext,
  loader_name: String,
  keys: List(dynamic.Dynamic),
) -> #(ExecutionContext, List(Result(dynamic.Dynamic, String)))

Load multiple values using a named DataLoader

Example

let keys = list.map([1, 2, 3], dataloader.int_key)
let #(ctx, results) = schema.load_many(ctx, "pokemon", keys)
pub fn load_many_by_id(
  context: ExecutionContext,
  loader_name: String,
  ids: List(Int),
) -> #(ExecutionContext, List(Result(dynamic.Dynamic, String)))

Load multiple entities by Int IDs using a named DataLoader

Example

let #(ctx, pokemon_list) = schema.load_many_by_id(ctx, "pokemon", [1, 4, 7, 25])
pub fn mutation(
  schema: Schema,
  mutation_type: ObjectType,
) -> Schema
pub fn named_type(name: String) -> FieldType
pub fn non_null(inner: FieldType) -> FieldType
pub fn object(name: String) -> ObjectType
pub fn parse_literal(
  scalar: ScalarType,
  parse_fn: fn(dynamic.Dynamic) -> Result(dynamic.Dynamic, String),
) -> ScalarType
pub fn parse_value(
  scalar: ScalarType,
  parse_fn: fn(dynamic.Dynamic) -> Result(dynamic.Dynamic, String),
) -> ScalarType
pub fn query(schema: Schema, query_type: ObjectType) -> Schema
pub fn query_with_args(
  obj: ObjectType,
  name: String,
  field_type: FieldType,
  args: List(ArgumentDefinition),
  desc: String,
  resolve_fn: fn(ResolverInfo) -> Result(dynamic.Dynamic, String),
) -> ObjectType

Add a query field with arguments

pub fn read_user_context(
  uc: UserContext,
  decoder: decode.Decoder(a),
) -> Result(a, String)

Read the wrapped value back, decoding into an app-defined type.

pub fn ref_field(
  obj: ObjectType,
  name: String,
  type_name: String,
) -> ObjectType

Add a reference field to another type with auto-resolver

pub fn ref_query(
  obj: ObjectType,
  name: String,
  type_name: String,
  desc: String,
  resolve_fn: fn(ResolverInfo) -> Result(dynamic.Dynamic, String),
) -> ObjectType

Add a reference query field: name: TypeName

pub fn required_bool_field(
  obj: ObjectType,
  name: String,
) -> ObjectType

Add a non-null Boolean field with auto-resolver

pub fn required_float_field(
  obj: ObjectType,
  name: String,
) -> ObjectType

Add a non-null Float field with auto-resolver

pub fn required_int_field(
  obj: ObjectType,
  name: String,
) -> ObjectType

Add a non-null Int field with auto-resolver

pub fn required_list_field(
  obj: ObjectType,
  name: String,
  item_type: String,
) -> ObjectType

Add a non-null list field with auto-resolver

pub fn required_ref_field(
  obj: ObjectType,
  name: String,
  type_name: String,
) -> ObjectType

Add a non-null reference field with auto-resolver

pub fn required_string_field(
  obj: ObjectType,
  name: String,
) -> ObjectType

Add a non-null String field with auto-resolver

pub fn resolved_columns(field: FieldDefinition) -> List(String)

The columns a field needs, applying the DerivedColumn default.

pub fn resolver(
  field: FieldDefinition,
  resolve_fn: fn(ResolverInfo) -> Result(dynamic.Dynamic, String),
) -> FieldDefinition
pub fn resolver_field(
  obj: ObjectType,
  name: String,
  field_type: FieldType,
  desc: String,
  resolve_fn: fn(ResolverInfo) -> Result(dynamic.Dynamic, String),
) -> ObjectType

Add a field with custom resolver and optional description

pub fn rich_resolver_fn(
  field: FieldDefinition,
  resolve_fn: fn(ResolverInfo) -> Result(
    dynamic.Dynamic,
    #(String, option.Option(dict.Dict(String, dynamic.Dynamic))),
  ),
) -> FieldDefinition
pub fn scalar(name: String) -> ScalarType
pub fn scalar_description(
  scalar: ScalarType,
  desc: String,
) -> ScalarType
pub fn schema() -> Schema
pub fn selected_field(
  name: String,
  children: List(SelectedField),
) -> SelectedField

An unconditional selected field with no arguments — the common shape when building a selection by hand in tests.

pub fn selection(
  context: ExecutionContext,
) -> List(SelectedField)

The fields the client selected on the value this resolver returns.

Computed on first read, so a resolver that never asks pays nothing. Each call recomputes, so bind it once and pipe it through mochi/selection:

let selected = schema.selection(ctx)
selected |> selection.columns(always: ["id"])
pub fn serialize(
  scalar: ScalarType,
  serialize_fn: fn(dynamic.Dynamic) -> Result(
    dynamic.Dynamic,
    String,
  ),
) -> ScalarType
pub fn skip_directive() -> DirectiveDefinition

@skip(if: Boolean!) directive

pub fn snake_case(name: String) -> String

fullName -> full_name, publishedAt -> published_at, userID -> user_id, HTTPStatus -> http_status. Already-snake names pass through.

pub fn string_field(obj: ObjectType, name: String) -> ObjectType

Add a nullable String field with auto-resolver

pub fn string_scalar() -> ScalarType
pub fn string_type() -> FieldType
pub fn subscription(
  schema: Schema,
  subscription_type: ObjectType,
) -> Schema
pub fn union(name: String) -> UnionType
pub fn union_description(
  union_type: UnionType,
  desc: String,
) -> UnionType
pub fn union_member(
  union_type: UnionType,
  member: ObjectType,
) -> UnionType
pub fn union_resolve_type(
  union_type: UnionType,
  resolver: fn(dynamic.Dynamic) -> Result(String, String),
) -> UnionType
pub fn update_data_loader(
  context: ExecutionContext,
  name: String,
  loader: dataloader.DataLoader(dynamic.Dynamic, dynamic.Dynamic),
) -> ExecutionContext

Update a DataLoader in the execution context

pub fn update_telemetry(
  context: ExecutionContext,
  telemetry: TelemetryContext,
) -> ExecutionContext

Update the telemetry context

pub fn url_type() -> FieldType
pub fn user_context(value: a) -> UserContext

Wrap an app-defined value so it can be carried through the executor.

pub fn uuid_type() -> FieldType
pub fn with_lazy_selection(
  context: ExecutionContext,
  compute: fn() -> List(SelectedField),
) -> ExecutionContext

Set a selection computed on first read. The executor uses this so a resolver that ignores the selection never pays for building it.

pub fn with_loaders(
  context: ExecutionContext,
  loaders: List(
    #(
      String,
      dataloader.DataLoader(dynamic.Dynamic, dynamic.Dynamic),
    ),
  ),
) -> ExecutionContext

Add multiple DataLoaders to the execution context at once

Example

let ctx = schema.execution_context(user_ctx)
  |> schema.with_loaders([
    #("pokemon", pokemon_loader),
    #("move", move_loader),
    #("trainer", trainer_loader),
  ])
pub fn with_middleware(
  context: ExecutionContext,
  middleware: fn(
    String,
    FieldDefinition,
    ResolverInfo,
    fn(ResolverInfo) -> Result(dynamic.Dynamic, String),
  ) -> Result(dynamic.Dynamic, String),
) -> ExecutionContext

Set middleware function on an execution context

pub fn with_selection(
  context: ExecutionContext,
  fields: List(SelectedField),
) -> ExecutionContext

Set the selection on a context — useful for testing a resolver’s projection logic without running a query.

pub fn with_telemetry(
  context: ExecutionContext,
  telemetry: TelemetryContext,
) -> ExecutionContext

Set telemetry on an execution context

pub fn with_telemetry_fn(
  context: ExecutionContext,
  telemetry_fn: fn(SchemaEvent) -> Nil,
) -> ExecutionContext

Set a telemetry callback function on the execution context. This function is called for each SchemaEvent emitted during execution. Use telemetry.to_schema_fn/1 to bridge a TelemetryConfig to this callback.

Search Document