TypedGql.TypeGenerator (TypedGql v0.13.0)

Copy Markdown View Source

Generates EctoTypedSchema embedded schema modules from GraphQL query AST.

Given an operation definition and a schema, generates per-query output type modules with proper nesting, nullability, and field alias support.

Generation pipeline

Generation runs as four named steps:

  1. normalize — raw selections to canonical selections: expands fragment spreads, flattens inline fragments on object types, and propagates ancestor (inline-fragment / fragment-spread) directives down onto each field's directives.
  2. resolve — canonical selections + schema to a TypedGql.Generation.Schema tree. The whole tree is built before any lowering.
  3. lower — tree to {module, quoted_ast} pairs.
  4. create{module, ast} pairs to BEAM modules.

TypedGql.Generation.Plugin documents which steps a plugin may hook and in what order the built-in and user plugins run.

Naming convention

Output types follow per-query path naming under a Result namespace:

ClientModule.FunctionName.Result.FieldName.NestedField...

Field aliases override both struct field names and module path segments.

Lists of objects

Only [T!]! becomes embeds_many: Ecto loads a null many-embed as [] and raises on a null element, so that is the one shape it models faithfully. Every other list of a composite — [T], [T]!, [T!], and any nesting such as [[T]] — becomes a plain field over the parameterized Ecto.Embedded type with cardinality: :one, which loads nil as nil at every level.

A [T!]! carrying @skip/@include is a plain field too: the response can omit it entirely, and embeds_many pins default: [], which would report a list the server never sent as an empty one.

A list the schema declares non-null — [T!]! and [T]! alike — defaults to [] rather than nil, since its typespec carries no | nil. Only a conditional one keeps a nil default, for the same reason it loses embeds_many.

Union/Interface support

When a field's type is a union or interface, inline fragments determine which concrete types to generate. Shared fields (outside fragments) are merged into each concrete type's struct. A parameterized TypedGql.Types.Union Ecto Type handles __typename-based dispatch during deserialization.

Summary

Functions

Generates embedded schema modules for an operation's output types.

Generates the result modules for a named fragment under ClientModule.Fragments.FragmentName.

Types

option()

@type option() ::
  {:client_module, module()}
  | {:function_name, atom()}
  | {:scalar_types, map()}
  | {:fragments, %{required(String.t()) => TypedGql.Language.Fragment.t()}}
  | {:generation_plugins, [module()]}
  | {:caller_env, Macro.Env.t()}

Functions

generate(operation, schema, opts)

Generates embedded schema modules for an operation's output types.

Returns a list of generated module names.

Options

  • :client_module — the parent client module (e.g., MyApp.UserService)
  • :function_name — the defgql function name (e.g., :get_user)
  • :scalar_types — custom scalar type mappings (default: %{})
  • :fragmentsTypedGql.Language.Fragment definitions by name, for spread expansion; a spread naming one that is absent raises CompileError (default: %{})
  • :generation_plugins — user TypedGql.Generation.Plugin modules, appended after the built-in plugins (default: [])
  • :caller_env — the macro caller's Macro.Env, used to set generated modules' source location for editor "go to definition" support

generate_fragment(fragment, schema, client_module, opts)

@spec generate_fragment(
  TypedGql.Language.Fragment.t(),
  TypedGql.Schema.t(),
  module(),
  [option()]
) ::
  module()

Generates the result modules for a named fragment under ClientModule.Fragments.FragmentName.

Takes the same options as generate/3 except :client_module and :function_name, which the fragment's own name replaces.

This was generate_fragment/5 up to 0.12.2, taking scalar_types as a bare fourth argument and the plugin list as a fifth. Both are options now, so a caller passing scalar_types positionally has to wrap it: generate_fragment(fragment, schema, client_module, scalar_types: types).

Returns the module that stands for the fragment's result. For an object type condition — and for an abstract one whose selections every member shares — that is the embedded schema at Fragments.FragmentName. For a condition with per-member selections there is no single struct: the return is the TypedGql.Types.Union parameterized type at Fragments.FragmentName.Union, which dispatches on __typename to the per-member embedded schemas at Fragments.FragmentName.MemberType.