Current section
Files
Jump to
Current section
Files
src/gleamql@operation.erl
-module(gleamql@operation).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleamql/operation.gleam").
-export(['query'/1, mutation/1, anonymous_query/0, anonymous_mutation/0, variable/3, fragment/2, build/1, to_string/1, variable_names/1, build_variables/2, field/2, root/2, decoder/1]).
-export_type([operation/1, operation_type/0, variable_def/0, operation_builder/1]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Operation builders for constructing GraphQL queries and mutations.\n"
"\n"
" This module provides functions for building complete GraphQL operations\n"
" with variable definitions and root fields. It supports both single and\n"
" multiple root field operations.\n"
"\n"
" ## Basic Usage - Single Root Field\n"
"\n"
" ```gleam\n"
" import gleamql/operation\n"
" import gleamql/field\n"
"\n"
" let country_op = \n"
" operation.query(\"CountryQuery\")\n"
" |> operation.variable(\"code\", \"ID!\")\n"
" |> operation.field(country_field())\n"
" ```\n"
"\n"
" ## Multiple Root Fields\n"
"\n"
" Query multiple fields at the root level while maintaining type safety:\n"
"\n"
" ```gleam\n"
" operation.query(\"GetDashboard\")\n"
" |> operation.variable(\"userId\", \"ID!\")\n"
" |> operation.root(fn() {\n"
" use user <- field.field(user_field())\n"
" use posts <- field.field(posts_field())\n"
" use stats <- field.field(stats_field())\n"
" field.build(#(user, posts, stats))\n"
" })\n"
" ```\n"
"\n"
" This generates clean GraphQL without wrapper fields:\n"
" ```graphql\n"
" query GetDashboard($userId: ID!) {\n"
" user { ... }\n"
" posts { ... }\n"
" stats { ... }\n"
" }\n"
" ```\n"
"\n"
).
-opaque operation(HRW) :: {operation,
operation_type(),
gleam@option:option(binary()),
list(variable_def()),
gleamql@field:field(HRW),
binary(),
list(binary()),
list(binary())}.
-type operation_type() :: 'query' | mutation.
-type variable_def() :: {variable_def, binary(), binary()}.
-opaque operation_builder(HRX) :: {operation_builder,
operation_type(),
gleam@option:option(binary()),
list(variable_def()),
list(binary())} |
{gleam_phantom, HRX}.
-file("src/gleamql/operation.gleam", 105).
?DOC(
" Create a named query operation.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" operation.query(\"GetCountry\")\n"
" |> operation.variable(\"code\", \"ID!\")\n"
" |> operation.field(country_field())\n"
" |> operation.build()\n"
" ```\n"
).
-spec 'query'(binary()) -> operation_builder(any()).
'query'(Name) ->
{operation_builder, 'query', {some, Name}, [], []}.
-file("src/gleamql/operation.gleam", 125).
?DOC(
" Create a named mutation operation.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" operation.mutation(\"CreatePost\")\n"
" |> operation.variable(\"input\", \"CreatePostInput!\")\n"
" |> operation.field(create_post_field())\n"
" |> operation.build()\n"
" ```\n"
).
-spec mutation(binary()) -> operation_builder(any()).
mutation(Name) ->
{operation_builder, mutation, {some, Name}, [], []}.
-file("src/gleamql/operation.gleam", 146).
?DOC(
" Create an anonymous query operation.\n"
"\n"
" Anonymous operations have no name and are useful for simple queries.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" operation.anonymous_query()\n"
" |> operation.field(countries_field())\n"
" |> operation.build()\n"
" ```\n"
).
-spec anonymous_query() -> operation_builder(any()).
anonymous_query() ->
{operation_builder, 'query', none, [], []}.
-file("src/gleamql/operation.gleam", 165).
?DOC(
" Create an anonymous mutation operation.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" operation.anonymous_mutation()\n"
" |> operation.field(create_post_field())\n"
" |> operation.build()\n"
" ```\n"
).
-spec anonymous_mutation() -> operation_builder(any()).
anonymous_mutation() ->
{operation_builder, mutation, none, [], []}.
-file("src/gleamql/operation.gleam", 195).
?DOC(
" Add a variable definition to the operation.\n"
"\n"
" Variables allow you to parameterize your operations and reuse them\n"
" with different values.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" operation.query(\"GetCountry\")\n"
" |> operation.variable(\"code\", \"ID!\") // Non-null ID\n"
" |> operation.variable(\"lang\", \"String\") // Optional String\n"
" ```\n"
"\n"
" The type definition should be a valid GraphQL type:\n"
" - Scalars: `\"String\"`, `\"Int\"`, `\"Float\"`, `\"Boolean\"`, `\"ID\"`\n"
" - Non-null: `\"String!\"`, `\"ID!\"`\n"
" - Lists: `\"[String]\"`, `\"[ID!]!\"` \n"
" - Custom types: `\"CreatePostInput!\"`, `\"[UserInput!]\"`\n"
).
-spec variable(operation_builder(HSG), binary(), binary()) -> operation_builder(HSG).
variable(Builder, Name, Type_def) ->
{operation_builder, Op_type, Op_name, Vars, Frags} = Builder,
New_var = {variable_def, Name, Type_def},
{operation_builder, Op_type, Op_name, [New_var | Vars], Frags}.
-file("src/gleamql/operation.gleam", 259).
?DOC(
" Add a fragment definition to the operation (optional).\n"
"\n"
" **Note:** As of version 1.0.0, fragments are automatically collected when\n"
" you use `fragment.spread()`, so this function is **optional**. You only need\n"
" to use it if you want to explicitly register a fragment that isn't used in\n"
" the current operation's fields.\n"
"\n"
" For most use cases, simply use `fragment.spread()` in your field selections\n"
" and the fragment will be automatically included.\n"
"\n"
" ## Example (modern approach - auto-collection)\n"
"\n"
" ```gleam\n"
" import gleamql/fragment\n"
"\n"
" let user_fields = fragment.on(\"User\", \"UserFields\", fn() {\n"
" use id <- field.field(field.id(\"id\"))\n"
" use name <- field.field(field.string(\"name\"))\n"
" field.build(User(id:, name:))\n"
" })\n"
"\n"
" // No need to call operation.fragment() - it's auto-collected!\n"
" operation.query(\"GetUser\")\n"
" |> operation.variable(\"id\", \"ID!\")\n"
" |> operation.field(\n"
" field.object(\"user\", fn() {\n"
" use user_data <- field.field(fragment.spread(user_fields))\n"
" field.build(user_data)\n"
" })\n"
" )\n"
" ```\n"
"\n"
" ## Example (legacy approach - manual registration)\n"
"\n"
" ```gleam\n"
" // You can still manually register if needed\n"
" operation.query(\"GetUser\")\n"
" |> operation.fragment(user_fields) // Optional - for backwards compatibility\n"
" |> operation.variable(\"id\", \"ID!\")\n"
" |> operation.field(user_field())\n"
" ```\n"
).
-spec fragment(operation_builder(HSJ), gleamql@fragment:fragment(any())) -> operation_builder(HSJ).
fragment(Builder, Frag) ->
{operation_builder, Op_type, Op_name, Vars, Frags} = Builder,
Frag_def = gleamql@fragment:to_definition(Frag),
{operation_builder, Op_type, Op_name, Vars, [Frag_def | Frags]}.
-file("src/gleamql/operation.gleam", 411).
?DOC(" Alias for `field` that builds the operation.\n").
-spec build(operation(HSX)) -> operation(HSX).
build(Operation) ->
Operation.
-file("src/gleamql/operation.gleam", 423).
-spec do_dedupe(list(binary()), list(binary())) -> list(binary()).
do_dedupe(Strings, Seen) ->
case Strings of
[] ->
lists:reverse(Seen);
[First | Rest] ->
case gleam@list:contains(Seen, First) of
true ->
do_dedupe(Rest, Seen);
false ->
do_dedupe(Rest, [First | Seen])
end
end.
-file("src/gleamql/operation.gleam", 419).
?DOC(" Deduplicate a list of strings while preserving order.\n").
-spec dedupe_strings(list(binary())) -> list(binary()).
dedupe_strings(Strings) ->
do_dedupe(Strings, []).
-file("src/gleamql/operation.gleam", 488).
?DOC(" Get the GraphQL query string from an operation.\n").
-spec to_string(operation(any())) -> binary().
to_string(Operation) ->
erlang:element(6, Operation).
-file("src/gleamql/operation.gleam", 524).
?DOC(
" Get the list of variable names defined in the operation.\n"
"\n"
" This is useful for knowing which variables need values at send time.\n"
).
-spec variable_names(operation(any())) -> list(binary()).
variable_names(Operation) ->
erlang:element(7, Operation).
-file("src/gleamql/operation.gleam", 533).
?DOC(
" Build the variables JSON object for the GraphQL request.\n"
"\n"
" Takes a list of variable name/value pairs and constructs the\n"
" variables object to send with the request.\n"
).
-spec build_variables(operation(any()), list({binary(), gleam@json:json()})) -> gleam@json:json().
build_variables(_, Values) ->
gleam@json:object(Values).
-file("src/gleamql/operation.gleam", 437).
?DOC(" Build the complete GraphQL query string.\n").
-spec build_query_string(
operation_type(),
gleam@option:option(binary()),
list(variable_def()),
gleamql@field:field(any()),
list(binary())
) -> binary().
build_query_string(Op_type, Op_name, Vars, Root_field, Fragments) ->
Operation_keyword = case Op_type of
'query' ->
<<"query"/utf8>>;
mutation ->
<<"mutation"/utf8>>
end,
Name_part = case Op_name of
{some, Name} ->
<<" "/utf8, Name/binary>>;
none ->
<<""/utf8>>
end,
Variables_part = case Vars of
[] ->
<<""/utf8>>;
Vars@1 ->
Vars_string = begin
_pipe = Vars@1,
_pipe@1 = lists:reverse(_pipe),
_pipe@2 = gleam@list:map(
_pipe@1,
fun(Var) ->
<<<<<<"$"/utf8, (erlang:element(2, Var))/binary>>/binary,
": "/utf8>>/binary,
(erlang:element(3, Var))/binary>>
end
),
gleam@string:join(_pipe@2, <<", "/utf8>>)
end,
<<<<"("/utf8, Vars_string/binary>>/binary, ")"/utf8>>
end,
Selection = gleamql@field:to_selection(Root_field),
Fragments_part = case Fragments of
[] ->
<<""/utf8>>;
Frags ->
<<"\n\n"/utf8,
(gleam@string:join(lists:reverse(Frags), <<"\n\n"/utf8>>))/binary>>
end,
<<<<<<<<<<<<Operation_keyword/binary, Name_part/binary>>/binary,
Variables_part/binary>>/binary,
" { "/utf8>>/binary,
Selection/binary>>/binary,
" }"/utf8>>/binary,
Fragments_part/binary>>.
-file("src/gleamql/operation.gleam", 295).
?DOC(
" Set the root field for the operation and build the final Operation.\n"
"\n"
" This completes the operation builder and generates the GraphQL query string.\n"
" Fragments used in the field tree are automatically collected and included\n"
" in the operation.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" operation.query(\"GetCountry\")\n"
" |> operation.variable(\"code\", \"ID!\")\n"
" |> operation.field(country_field())\n"
" |> operation.build()\n"
" ```\n"
).
-spec field(operation_builder(HSO), gleamql@field:field(HSO)) -> operation(HSO).
field(Builder, Root_field) ->
{operation_builder, Op_type, Op_name, Vars, Manual_frags} = Builder,
Field_frags = gleamql@field:fragments(Root_field),
All_frags = begin
_pipe = lists:append(Manual_frags, Field_frags),
dedupe_strings(_pipe)
end,
Query_string = build_query_string(
Op_type,
Op_name,
Vars,
Root_field,
All_frags
),
Variables_list = begin
_pipe@1 = Vars,
gleam@list:map(_pipe@1, fun(Var) -> erlang:element(2, Var) end)
end,
{operation,
Op_type,
Op_name,
Vars,
Root_field,
Query_string,
Variables_list,
All_frags}.
-file("src/gleamql/operation.gleam", 383).
?DOC(
" Set multiple root fields for the operation using a builder pattern.\n"
"\n"
" This allows you to query multiple root fields in a single operation\n"
" while maintaining full type safety. The builder pattern is identical to\n"
" `field.object()`, but the generated GraphQL will not wrap the fields\n"
" in an additional object.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" pub type UserAndPosts {\n"
" UserAndPosts(user: User, posts: List(Post))\n"
" }\n"
"\n"
" operation.query(\"GetData\")\n"
" |> operation.variable(\"userId\", \"ID!\")\n"
" |> operation.root(fn() {\n"
" use user <- field.field(\n"
" field.object(\"user\", user_builder)\n"
" |> field.arg(\"id\", \"userId\")\n"
" )\n"
" use posts <- field.field(\n"
" field.list(field.object(\"posts\", post_builder))\n"
" |> field.arg_int(\"limit\", 10)\n"
" )\n"
" field.build(UserAndPosts(user:, posts:))\n"
" })\n"
" ```\n"
"\n"
" Generates:\n"
" ```graphql\n"
" query GetData($userId: ID!) {\n"
" user(id: $userId) { ... }\n"
" posts(limit: 10) { ... }\n"
" }\n"
" ```\n"
"\n"
" ## Single Root Field\n"
"\n"
" You can also use `root()` with a single field (though `field()` is simpler):\n"
"\n"
" ```gleam\n"
" operation.root(fn() {\n"
" use user <- field.field(user_field())\n"
" field.build(user)\n"
" })\n"
" ```\n"
"\n"
" ## Backward Compatibility\n"
"\n"
" The existing `field()` function continues to work for single-field operations.\n"
" Use `root()` when you need multiple root fields or want a consistent API.\n"
).
-spec root(
operation_builder(any()),
fun(() -> gleamql@field:object_builder(HSU))
) -> operation(HSU).
root(Builder, Root_builder) ->
Phantom_field = gleamql@field:phantom_root(Root_builder),
{operation_builder, Op_type, Op_name, Vars, Manual_frags} = Builder,
Typed_builder = {operation_builder, Op_type, Op_name, Vars, Manual_frags},
field(Typed_builder, Phantom_field).
-file("src/gleamql/operation.gleam", 497).
?DOC(
" Get the decoder from an operation.\n"
"\n"
" This decoder will automatically unwrap the \"data\" field from the\n"
" GraphQL response.\n"
).
-spec decoder(operation(HTM)) -> gleam@dynamic@decode:decoder(HTM).
decoder(Operation) ->
Root_field_decoder = gleamql@field:decoder(erlang:element(5, Operation)),
case gleamql@field:is_phantom_root(erlang:element(5, Operation)) of
true ->
gleam@dynamic@decode:field(
<<"data"/utf8>>,
Root_field_decoder,
fun(Data_value) -> gleam@dynamic@decode:success(Data_value) end
);
false ->
Root_field_name = gleamql@field:name(erlang:element(5, Operation)),
gleam@dynamic@decode:field(
<<"data"/utf8>>,
begin
gleam@dynamic@decode:field(
Root_field_name,
Root_field_decoder,
fun(Field_value) ->
gleam@dynamic@decode:success(Field_value)
end
)
end,
fun(Data_value@1) ->
gleam@dynamic@decode:success(Data_value@1)
end
)
end.