Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relay refetching support #106

Merged
merged 5 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions documentation/topics/relay.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,14 @@ Use the following option when calling `use AshGraphql`
```elixir
use AshGraphql, define_relay_types?: false
```

## Relay Global IDs

Use the following option to generate Relay Global IDs (see
[here](https://relay.dev/graphql/objectidentification.htm)).

```elixir
use AshGraphql, relay_ids?: true
```

This allows refetching a node using the `node` query and passing its global ID.
18 changes: 12 additions & 6 deletions lib/api/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -72,28 +72,33 @@ defmodule AshGraphql.Api do
defdelegate debug?(api), to: AshGraphql.Api.Info

@doc false
def queries(api, resources, action_middleware, schema) do
Enum.flat_map(resources, &AshGraphql.Resource.queries(api, &1, action_middleware, schema))
def queries(api, resources, action_middleware, schema, relay_ids?) do
Enum.flat_map(
resources,
&AshGraphql.Resource.queries(api, &1, action_middleware, schema, relay_ids?)
)
end

@doc false
def mutations(api, resources, action_middleware, schema) do
def mutations(api, resources, action_middleware, schema, relay_ids?) do
resources
|> Enum.filter(fn resource ->
AshGraphql.Resource in Spark.extensions(resource)
end)
|> Enum.flat_map(&AshGraphql.Resource.mutations(api, &1, action_middleware, schema))
|> Enum.flat_map(
&AshGraphql.Resource.mutations(api, &1, action_middleware, schema, relay_ids?)
)
end

@doc false
def type_definitions(api, resources, schema, env, first?, define_relay_types?) do
def type_definitions(api, resources, schema, env, first?, define_relay_types?, relay_ids?) do
resource_types =
resources
|> Enum.reject(&Ash.Resource.Info.embedded?/1)
|> Enum.flat_map(fn resource ->
if AshGraphql.Resource in Spark.extensions(resource) &&
AshGraphql.Resource.Info.type(resource) do
AshGraphql.Resource.type_definitions(resource, api, schema) ++
AshGraphql.Resource.type_definitions(resource, api, schema, relay_ids?) ++
AshGraphql.Resource.mutation_types(resource, schema)
else
AshGraphql.Resource.no_graphql_types(resource, schema)
Expand All @@ -118,6 +123,7 @@ defmodule AshGraphql.Api do
}
],
identifier: :node,
resolve_type: &AshGraphql.Graphql.Resolver.resolve_node_type/2,
__reference__: AshGraphql.Resource.ref(env),
module: schema
},
Expand Down
91 changes: 82 additions & 9 deletions lib/ash_graphql.ex
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ defmodule AshGraphql do
apis: opts[:apis],
api: opts[:api],
action_middleware: opts[:action_middleware] || [],
define_relay_types?: Keyword.get(opts, :define_relay_types?, true)
define_relay_types?: Keyword.get(opts, :define_relay_types?, true),
relay_ids?: Keyword.get(opts, :relay_ids?, false)
],
generated: true do
require Ash.Api.Info
Expand Down Expand Up @@ -136,16 +137,37 @@ defmodule AshGraphql do
api = unquote(api)
action_middleware = unquote(action_middleware)

api_queries =
AshGraphql.Api.queries(
api,
unquote(resources),
action_middleware,
__MODULE__,
unquote(relay_ids?)
)

relay_queries =
if unquote(first?) and unquote(define_relay_types?) and unquote(relay_ids?) do
apis_with_resources = unquote(Enum.map(apis, &{elem(&1, 0), elem(&1, 1)}))
AshGraphql.relay_queries(apis_with_resources, unquote(schema), __ENV__)
else
[]
end

blueprint_with_queries =
api
|> AshGraphql.Api.queries(unquote(resources), action_middleware, __MODULE__)
(relay_queries ++ api_queries)
|> Enum.reduce(blueprint, fn query, blueprint ->
Absinthe.Blueprint.add_field(blueprint, "RootQueryType", query)
end)

blueprint_with_mutations =
api
|> AshGraphql.Api.mutations(unquote(resources), action_middleware, __MODULE__)
|> AshGraphql.Api.mutations(
unquote(resources),
action_middleware,
__MODULE__,
unquote(relay_ids?)
)
|> Enum.reduce(blueprint_with_queries, fn mutation, blueprint ->
Absinthe.Blueprint.add_field(blueprint, "RootMutationType", mutation)
end)
Expand All @@ -155,7 +177,11 @@ defmodule AshGraphql do
apis = unquote(Enum.map(apis, &elem(&1, 0)))

embedded_types =
AshGraphql.get_embedded_types(unquote(ash_resources), unquote(schema))
AshGraphql.get_embedded_types(
unquote(ash_resources),
unquote(schema),
unquote(relay_ids?)
)

global_enums =
AshGraphql.global_enums(unquote(ash_resources), unquote(schema), __ENV__)
Expand All @@ -171,7 +197,8 @@ defmodule AshGraphql do
unquote(schema),
__ENV__,
true,
unquote(define_relay_types?)
unquote(define_relay_types?),
unquote(relay_ids?)
) ++
global_enums ++
global_unions ++
Expand All @@ -185,7 +212,8 @@ defmodule AshGraphql do
unquote(schema),
__ENV__,
false,
false
false,
unquote(relay_ids?)
)
end

Expand Down Expand Up @@ -334,6 +362,50 @@ defmodule AshGraphql do
end
end

def relay_queries(apis_with_resources, schema, env) do
type_to_api_and_resource_map =
apis_with_resources
|> Enum.flat_map(fn {api, resources} ->
resources
|> Enum.flat_map(fn resource ->
type = AshGraphql.Resource.Info.type(resource)

if type do
[{type, {api, resource}}]
else
[]
end
end)
end)
|> Enum.into(%{})

[
%Absinthe.Blueprint.Schema.FieldDefinition{
name: "node",
identifier: :node,
arguments: [
%Absinthe.Blueprint.Schema.InputValueDefinition{
name: "id",
identifier: :id,
type: %Absinthe.Blueprint.TypeReference.NonNull{
of_type: :id
},
description: "The Node unique identifier",
__reference__: AshGraphql.Resource.ref(env)
}
],
middleware: [
{{AshGraphql.Graphql.Resolver, :resolve_node}, type_to_api_and_resource_map}
],
complexity: {AshGraphql.Graphql.Resolver, :query_complexity},
module: schema,
description: "Retrieves a Node from its global id",
type: %Absinthe.Blueprint.TypeReference.NonNull{of_type: :node},
__reference__: AshGraphql.Resource.ref(__ENV__)
}
]
end

defp nested_attrs({:array, type}, constraints, already_checked) do
nested_attrs(type, constraints[:items] || [], already_checked)
end
Expand Down Expand Up @@ -491,7 +563,7 @@ defmodule AshGraphql do
end

# sobelow_skip ["DOS.BinToAtom"]
def get_embedded_types(all_resources, schema) do
def get_embedded_types(all_resources, schema, relay_ids?) do
all_resources
|> Enum.flat_map(fn resource ->
resource
Expand Down Expand Up @@ -566,7 +638,8 @@ defmodule AshGraphql do
AshGraphql.Resource.type_definition(
embedded_type,
Module.concat(embedded_type, ShadowApi),
schema
schema,
relay_ids?
),
AshGraphql.Resource.embedded_type_input(
source_resource,
Expand Down
12 changes: 12 additions & 0 deletions lib/error.ex
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,15 @@ defimpl AshGraphql.Error, for: Ash.Error.Forbidden.ForbiddenField do
}
end
end

defimpl AshGraphql.Error, for: Ash.Error.Invalid.InvalidPrimaryKey do
def to_error(error) do
%{
message: "invalid primary key provided",
short_message: "invalid primary key provided",
fields: [],
vars: Map.new(error.vars),
code: Ash.ErrorKind.code(error)
}
end
end
Loading
Loading