mirror of
https://github.com/ash-project/ash_graphql.git
synced 2024-09-19 12:53:40 +12:00
improvement: generic action support
docs: better docs
This commit is contained in:
parent
0ec762507f
commit
49c6534d73
24 changed files with 1111 additions and 229 deletions
|
@ -11,7 +11,8 @@
|
||||||
## ...or adjusted (e.g. use one-line formatter for more compact credo output)
|
## ...or adjusted (e.g. use one-line formatter for more compact credo output)
|
||||||
# {:credo, "mix credo --format oneline"},
|
# {:credo, "mix credo --format oneline"},
|
||||||
|
|
||||||
{:check_formatter, command: "mix spark.formatter --check"}
|
{:check_formatter, command: "mix spark.formatter --check"},
|
||||||
|
{:doctor, false}
|
||||||
|
|
||||||
## custom new tools may be added (mix tasks or arbitrary commands)
|
## custom new tools may be added (mix tasks or arbitrary commands)
|
||||||
# {:my_mix_task, command: "mix release", env: %{"MIX_ENV" => "prod"}},
|
# {:my_mix_task, command: "mix release", env: %{"MIX_ENV" => "prod"}},
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
spark_locals_without_parens = [
|
spark_locals_without_parens = [
|
||||||
|
action: 2,
|
||||||
|
action: 3,
|
||||||
allow_nil?: 1,
|
allow_nil?: 1,
|
||||||
argument_names: 1,
|
argument_names: 1,
|
||||||
as_mutation?: 1,
|
as_mutation?: 1,
|
||||||
|
|
37
documentation/dsls/DSL:-AshGraphql.Api.cheatmd
Normal file
37
documentation/dsls/DSL:-AshGraphql.Api.cheatmd
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# DSL: AshGraphql.Api
|
||||||
|
|
||||||
|
The entrypoint for adding graphql behavior to an Ash API
|
||||||
|
|
||||||
|
|
||||||
|
## graphql
|
||||||
|
Global configuration for graphql
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
```
|
||||||
|
graphql do
|
||||||
|
authorize? false # To skip authorization for this API
|
||||||
|
end
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Options
|
||||||
|
| Name | Type | Default | Docs |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `authorize?` | `boolean` | true | Whether or not to perform authorization for this API |
|
||||||
|
| `tracer` | `atom` | | A tracer to use to trace execution in the graphql. Will use `config :ash, :tracer` if it is set. |
|
||||||
|
| `root_level_errors?` | `boolean` | false | By default, mutation errors are shown in their result object's errors key, but this setting places those errors in the top level errors list |
|
||||||
|
| `error_handler` | `mfa` | {AshGraphql.DefaultErrorHandler, :handle_error, []} | Set an MFA to intercept/handle any errors that are generated. |
|
||||||
|
| `show_raised_errors?` | `boolean` | false | For security purposes, if an error is *raised* then Ash simply shows a generic error. If you want to show those errors, set this to true. |
|
||||||
|
| `debug?` | `boolean` | false | Whether or not to log (extremely verbose) debug information |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
523
documentation/dsls/DSL:-AshGraphql.Resource.cheatmd
Normal file
523
documentation/dsls/DSL:-AshGraphql.Resource.cheatmd
Normal file
|
@ -0,0 +1,523 @@
|
||||||
|
# DSL: AshGraphql.Resource
|
||||||
|
|
||||||
|
This Ash resource extension adds configuration for exposing a resource in a graphql.
|
||||||
|
|
||||||
|
|
||||||
|
## graphql
|
||||||
|
Configuration for a given resource in graphql
|
||||||
|
|
||||||
|
|
||||||
|
### Nested DSLs
|
||||||
|
* [queries](#graphql-queries)
|
||||||
|
* get
|
||||||
|
* read_one
|
||||||
|
* list
|
||||||
|
* action
|
||||||
|
* [mutations](#graphql-mutations)
|
||||||
|
* create
|
||||||
|
* update
|
||||||
|
* destroy
|
||||||
|
* action
|
||||||
|
* [managed_relationships](#graphql-managed_relationships)
|
||||||
|
* managed_relationship
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
```
|
||||||
|
graphql do
|
||||||
|
type :post
|
||||||
|
|
||||||
|
queries do
|
||||||
|
get :get_post, :read
|
||||||
|
list :list_posts, :read
|
||||||
|
end
|
||||||
|
|
||||||
|
mutations do
|
||||||
|
create :create_post, :create
|
||||||
|
update :update_post, :update
|
||||||
|
destroy :destroy_post, :destroy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Options
|
||||||
|
| Name | Type | Default | Docs |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `type`* | `atom` | | The type to use for this entity in the graphql schema |
|
||||||
|
| `derive_filter?` | `boolean` | true | Set to false to disable the automatic generation of a filter input for read actions. |
|
||||||
|
| `derive_sort?` | `boolean` | true | Set to false to disable the automatic generation of a sort input for read actions. |
|
||||||
|
| `encode_primary_key?` | `boolean` | true | For resources with composite primary keys, or primary keys not called `:id`, this will cause the id to be encoded as a single `id` attribute, both in the representation of the resource and in get requests |
|
||||||
|
| `relationships` | `list(atom)` | | A list of relationships to include on the created type. Defaults to all public relationships where the destination defines a graphql type. |
|
||||||
|
| `field_names` | `Keyword.t` | | A keyword list of name overrides for attributes. |
|
||||||
|
| `hide_fields` | `list(atom)` | | A list of attributes to hide from the api |
|
||||||
|
| `argument_names` | `Keyword.t` | | A nested keyword list of action names, to argument name remappings. i.e `create: [arg_name: :new_name]` |
|
||||||
|
| `keyset_field` | `atom` | | If set, the keyset will be displayed on all read actions in this field. It will be `nil` unless at least one of the read actions on a resource uses keyset pagination or it is the result of a mutation |
|
||||||
|
| `attribute_types` | `Keyword.t` | | A keyword list of type overrides for attributes. The type overrides should refer to types available in the graphql (absinthe) schema. `list_of/1` and `non_null/1` helpers can be used. |
|
||||||
|
| `attribute_input_types` | `Keyword.t` | | A keyword list of input type overrides for attributes. The type overrides should refer to types available in the graphql (absinthe) schema. `list_of/1` and `non_null/1` helpers can be used. |
|
||||||
|
| `primary_key_delimiter` | `String.t` | "~" | If a composite primary key exists, this can be set to determine delimiter used in the `id` field value. |
|
||||||
|
| `depth_limit` | `integer` | | A simple way to prevent massive queries. |
|
||||||
|
| `generate_object?` | `boolean` | true | Whether or not to create the GraphQL object, this allows you to manually create the GraphQL object. |
|
||||||
|
| `filterable_fields` | `list(atom)` | | A list of fields that are allowed to be filtered on. Defaults to all filterable fields for which a GraphQL type can be created. |
|
||||||
|
|
||||||
|
|
||||||
|
## graphql.queries
|
||||||
|
Queries (read actions) to expose for the resource.
|
||||||
|
|
||||||
|
|
||||||
|
### Nested DSLs
|
||||||
|
* [get](#graphql-queries-get)
|
||||||
|
* [read_one](#graphql-queries-read_one)
|
||||||
|
* [list](#graphql-queries-list)
|
||||||
|
* [action](#graphql-queries-action)
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
```
|
||||||
|
queries do
|
||||||
|
get :get_post, :read
|
||||||
|
read_one :current_user, :current_user
|
||||||
|
list :list_posts, :read
|
||||||
|
end
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## graphql.queries.get
|
||||||
|
```elixir
|
||||||
|
get name, action
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
A query to fetch a record by primary key
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
```
|
||||||
|
get :get_post, :read
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
| Name | Type | Default | Docs |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `name` | `atom` | :get | The name to use for the query. |
|
||||||
|
| `action`* | `atom` | | The action to use for the query. |
|
||||||
|
### Options
|
||||||
|
| Name | Type | Default | Docs |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `identity` | `atom` | | The identity to use for looking up the record. Pass `false` to not use an identity. |
|
||||||
|
| `allow_nil?` | `boolean` | true | Whether or not the action can return nil. |
|
||||||
|
| `modify_resolution` | `mfa` | | An MFA that will be called with the resolution, the query, and the result of the action as the first three arguments. See the [the guide](/documentation/topics/modifying-the-resolution.html) for more. |
|
||||||
|
| `type_name` | `atom` | | Override the type name returned by this query. Must be set if the read action has `metadata` that is not hidden via the `show_metadata` key. |
|
||||||
|
| `metadata_names` | `Keyword.t` | [] | Name overrides for metadata fields on the read action. |
|
||||||
|
| `metadata_types` | `Keyword.t` | [] | Type overrides for metadata fields on the read action. |
|
||||||
|
| `show_metadata` | `list(atom)` | | The metadata attributes to show. Defaults to all. |
|
||||||
|
| `as_mutation?` | `boolean` | false | Places the query in the `mutations` key instead. Not typically necessary, but is often paired with `as_mutation?`. See the [the guide](/documentation/topics/modifying-the-resolution.html) for more. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Introspection
|
||||||
|
|
||||||
|
Target: `AshGraphql.Resource.Query`
|
||||||
|
|
||||||
|
## graphql.queries.read_one
|
||||||
|
```elixir
|
||||||
|
read_one name, action
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
A query to fetch a record
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
```
|
||||||
|
read_one :current_user, :current_user
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
| Name | Type | Default | Docs |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `name` | `atom` | :get | The name to use for the query. |
|
||||||
|
| `action`* | `atom` | | The action to use for the query. |
|
||||||
|
### Options
|
||||||
|
| Name | Type | Default | Docs |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `allow_nil?` | `boolean` | true | Whether or not the action can return nil. |
|
||||||
|
| `type_name` | `atom` | | Override the type name returned by this query. Must be set if the read action has `metadata` that is not hidden via the `show_metadata` key. |
|
||||||
|
| `metadata_names` | `Keyword.t` | [] | Name overrides for metadata fields on the read action. |
|
||||||
|
| `metadata_types` | `Keyword.t` | [] | Type overrides for metadata fields on the read action. |
|
||||||
|
| `show_metadata` | `list(atom)` | | The metadata attributes to show. Defaults to all. |
|
||||||
|
| `as_mutation?` | `boolean` | false | Places the query in the `mutations` key instead. Not typically necessary, but is often paired with `as_mutation?`. See the [the guide](/documentation/topics/modifying-the-resolution.html) for more. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Introspection
|
||||||
|
|
||||||
|
Target: `AshGraphql.Resource.Query`
|
||||||
|
|
||||||
|
## graphql.queries.list
|
||||||
|
```elixir
|
||||||
|
list name, action
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
A query to fetch a list of records
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
```
|
||||||
|
list :list_posts, :read
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
list :list_posts_paginated, :read, relay?: true
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
| Name | Type | Default | Docs |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `name` | `atom` | :get | The name to use for the query. |
|
||||||
|
| `action`* | `atom` | | The action to use for the query. |
|
||||||
|
### Options
|
||||||
|
| Name | Type | Default | Docs |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `relay?` | `boolean` | false | If true, the graphql queries/resolvers for this resource will be built to honor the relay specification. See [the relay guide](/documentation/topics/relay.html) for more. |
|
||||||
|
| `type_name` | `atom` | | Override the type name returned by this query. Must be set if the read action has `metadata` that is not hidden via the `show_metadata` key. |
|
||||||
|
| `metadata_names` | `Keyword.t` | [] | Name overrides for metadata fields on the read action. |
|
||||||
|
| `metadata_types` | `Keyword.t` | [] | Type overrides for metadata fields on the read action. |
|
||||||
|
| `show_metadata` | `list(atom)` | | The metadata attributes to show. Defaults to all. |
|
||||||
|
| `as_mutation?` | `boolean` | false | Places the query in the `mutations` key instead. Not typically necessary, but is often paired with `as_mutation?`. See the [the guide](/documentation/topics/modifying-the-resolution.html) for more. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Introspection
|
||||||
|
|
||||||
|
Target: `AshGraphql.Resource.Query`
|
||||||
|
|
||||||
|
## graphql.queries.action
|
||||||
|
```elixir
|
||||||
|
action name, action
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Runs a generic action
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
```
|
||||||
|
action :check_status, :check_status
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
| Name | Type | Default | Docs |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `name` | `atom` | :get | The name to use for the query. |
|
||||||
|
| `action`* | `atom` | | The action to use for the query. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Introspection
|
||||||
|
|
||||||
|
Target: `AshGraphql.Resource.Action`
|
||||||
|
|
||||||
|
|
||||||
|
## graphql.mutations
|
||||||
|
Mutations (create/update/destroy actions) to expose for the resource.
|
||||||
|
|
||||||
|
|
||||||
|
### Nested DSLs
|
||||||
|
* [create](#graphql-mutations-create)
|
||||||
|
* [update](#graphql-mutations-update)
|
||||||
|
* [destroy](#graphql-mutations-destroy)
|
||||||
|
* [action](#graphql-mutations-action)
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
```
|
||||||
|
mutations do
|
||||||
|
create :create_post, :create
|
||||||
|
update :update_post, :update
|
||||||
|
destroy :destroy_post, :destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## graphql.mutations.create
|
||||||
|
```elixir
|
||||||
|
create name, action
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
A mutation to create a record
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
```
|
||||||
|
create :create_post, :create
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
| Name | Type | Default | Docs |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `name` | `atom` | :get | The name to use for the mutation. |
|
||||||
|
| `action`* | `atom` | | The action to use for the mutation. |
|
||||||
|
### Options
|
||||||
|
| Name | Type | Default | Docs |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `upsert?` | `boolean` | false | Whether or not to use the `upsert?: true` option when calling `YourApi.create/2`. |
|
||||||
|
| `upsert_identity` | `atom` | false | Which identity to use for the upsert |
|
||||||
|
| `modify_resolution` | `mfa` | | An MFA that will be called with the resolution, the query, and the result of the action as the first three arguments. See the [the guide](/documentation/topics/modifying-the-resolution.html) for more. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Introspection
|
||||||
|
|
||||||
|
Target: `AshGraphql.Resource.Mutation`
|
||||||
|
|
||||||
|
## graphql.mutations.update
|
||||||
|
```elixir
|
||||||
|
update name, action
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
A mutation to update a record
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
```
|
||||||
|
update :update_post, :update
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
| Name | Type | Default | Docs |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `name` | `atom` | :get | The name to use for the mutation. |
|
||||||
|
| `action`* | `atom` | | The action to use for the mutation. |
|
||||||
|
### Options
|
||||||
|
| Name | Type | Default | Docs |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `identity` | `atom` | | The identity to use to fetch the record to be updated. Use `false` if no identity is required. |
|
||||||
|
| `read_action` | `atom` | | The read action to use to fetch the record to be updated. Defaults to the primary read action. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Introspection
|
||||||
|
|
||||||
|
Target: `AshGraphql.Resource.Mutation`
|
||||||
|
|
||||||
|
## graphql.mutations.destroy
|
||||||
|
```elixir
|
||||||
|
destroy name, action
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
A mutation to destroy a record
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
```
|
||||||
|
destroy :destroy_post, :destroy
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
| Name | Type | Default | Docs |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `name` | `atom` | :get | The name to use for the mutation. |
|
||||||
|
| `action`* | `atom` | | The action to use for the mutation. |
|
||||||
|
### Options
|
||||||
|
| Name | Type | Default | Docs |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `read_action` | `atom` | | The read action to use to fetch the record to be destroyed. Defaults to the primary read action. |
|
||||||
|
| `identity` | `atom` | | The identity to use to fetch the record to be destroyed. Use `false` if no identity is required. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Introspection
|
||||||
|
|
||||||
|
Target: `AshGraphql.Resource.Mutation`
|
||||||
|
|
||||||
|
## graphql.mutations.action
|
||||||
|
```elixir
|
||||||
|
action name, action
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Runs a generic action
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
```
|
||||||
|
action :check_status, :check_status
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
| Name | Type | Default | Docs |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `name` | `atom` | :get | The name to use for the query. |
|
||||||
|
| `action`* | `atom` | | The action to use for the query. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Introspection
|
||||||
|
|
||||||
|
Target: `AshGraphql.Resource.Action`
|
||||||
|
|
||||||
|
|
||||||
|
## graphql.managed_relationships
|
||||||
|
Generates input objects for `manage_relationship` arguments on resource actions.
|
||||||
|
|
||||||
|
|
||||||
|
### Nested DSLs
|
||||||
|
* [managed_relationship](#graphql-managed_relationships-managed_relationship)
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
```
|
||||||
|
managed_relationships do
|
||||||
|
manage_relationship :create_post, :comments
|
||||||
|
end
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Options
|
||||||
|
| Name | Type | Default | Docs |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `auto?` | `boolean` | | Automatically derive types for all arguments that have a `manage_relationship` call change. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## graphql.managed_relationships.managed_relationship
|
||||||
|
```elixir
|
||||||
|
managed_relationship action, argument
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Instructs ash_graphql that a given argument with a `manage_relationship` change should have its input objects derived automatically from the potential actions to be called.
|
||||||
|
|
||||||
|
For example, given an action like:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
actions do
|
||||||
|
create :create do
|
||||||
|
argument :comments, {:array, :map}
|
||||||
|
|
||||||
|
change manage_relationship(:comments, type: :direct_control) # <- we look for this change with a matching argument name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
You could add the following managed_relationship
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
graphql do
|
||||||
|
...
|
||||||
|
|
||||||
|
managed_relationships do
|
||||||
|
managed_relationship :create, :comments
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, the `{:array, :map}` would simply be a `json[]` type. If the argument name
|
||||||
|
is placed in this list, all of the potential actions that could be called will be combined
|
||||||
|
into a single input object. If there are type conflicts (for example, if the input could create
|
||||||
|
or update a record, and the create and update actions have an argument of the same name but with a different type),
|
||||||
|
a warning is emitted at compile time and the first one is used. If that is insufficient, you will need to do one of the following:
|
||||||
|
|
||||||
|
1.) provide the `:types` option to the `managed_relationship` constructor (see that option for more)
|
||||||
|
2.) define a custom type, with a custom input object (see the custom types guide), and use that custom type instead of `:map`
|
||||||
|
3.) change your actions to not have overlapping inputs with different types
|
||||||
|
|
||||||
|
Since managed relationships can ultimately call multiple actions, there is the possibility
|
||||||
|
of field type conflicts. Use the `types` option to determine the type of fields and remove the conflict warnings.
|
||||||
|
|
||||||
|
For `non_null` use `{:non_null, type}`, and for a list, use `{:array, type}`, for example:
|
||||||
|
|
||||||
|
`{:non_null, {:array, {:non_null, :string}}}` for a non null list of non null strings.
|
||||||
|
|
||||||
|
To *remove* a key from the input object, simply pass `nil` as the type.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
| Name | Type | Default | Docs |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `action` | `atom` | | The action that accepts the argument |
|
||||||
|
| `argument`* | `atom` | | The argument for which an input object should be derived. |
|
||||||
|
### Options
|
||||||
|
| Name | Type | Default | Docs |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `lookup_with_primary_key?` | `boolean` | | If the managed_relationship has `on_lookup` behavior, this option determines whether or not the primary key is provided in the input object for looking up. |
|
||||||
|
| `lookup_identities` | `list(atom)` | | Determines which identities are provided in the input object for looking up, if there is `on_lookup` behavior. Defalts to the `use_identities` option. |
|
||||||
|
| `type_name` | `atom` | | The name of the input object that will be derived. Defaults to `<action_type>_<resource>_<argument_name>_input` |
|
||||||
|
| `types` | ``any`` | | A keyword list of field names to their graphql type identifiers. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Introspection
|
||||||
|
|
||||||
|
Target: `AshGraphql.Resource.ManagedRelationship`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
10
documentation/topics/modifying-the-resolution.md
Normal file
10
documentation/topics/modifying-the-resolution.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# Modifying the Resolution
|
||||||
|
|
||||||
|
Using the `modify_resolution` option, you can alter the Absinthe resolution.
|
||||||
|
|
||||||
|
`modify_resoltion` is an MFA that will be called with the resolution, the query, and the result of the action as the first three arguments. Must return a new absinthe resolution.
|
||||||
|
|
||||||
|
This can be used to implement things like setting cookies based on resource actions. A method of using resolution context
|
||||||
|
for that is documented here: https://hexdocs.pm/absinthe_plug/Absinthe.Plug.html#module-before-send
|
||||||
|
|
||||||
|
*Important* if you are modifying the context in a query, then you should also set `as_mutation?` to true and represent this in your graphql as a mutation. See `as_mutation?` for more.
|
8
documentation/topics/relay.md
Normal file
8
documentation/topics/relay.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Relay
|
||||||
|
|
||||||
|
Enabling relay for a resource sets it up to follow the [relay specification](https://relay.dev/graphql/connections.htm).
|
||||||
|
|
||||||
|
The two changes that are made currently are:
|
||||||
|
|
||||||
|
* the type for the resource will implement the `Node` interface
|
||||||
|
* pagination over that resource will behave as a Connection.
|
|
@ -53,19 +53,6 @@ defmodule AshGraphql.Api do
|
||||||
|
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
The entrypoint for adding graphql behavior to an Ash API
|
The entrypoint for adding graphql behavior to an Ash API
|
||||||
|
|
||||||
<!--- ash-hq-hide-start --> <!--- -->
|
|
||||||
|
|
||||||
## DSL Documentation
|
|
||||||
|
|
||||||
### Index
|
|
||||||
|
|
||||||
#{Spark.Dsl.Extension.doc_index(@sections)}
|
|
||||||
|
|
||||||
### Docs
|
|
||||||
|
|
||||||
#{Spark.Dsl.Extension.doc(@sections)}
|
|
||||||
<!--- ash-hq-hide-stop --> <!--- -->
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
require Ash.Api.Info
|
require Ash.Api.Info
|
||||||
|
|
|
@ -10,7 +10,10 @@ defmodule AshGraphql.Api.Info do
|
||||||
|
|
||||||
@doc "The tracer to use for the given schema"
|
@doc "The tracer to use for the given schema"
|
||||||
def tracer(api) do
|
def tracer(api) do
|
||||||
Extension.get_opt(api, [:graphql], :tracer, Application.get_env(:ash, :tracer), true)
|
api
|
||||||
|
|> Extension.get_opt([:graphql], :tracer, nil, true)
|
||||||
|
|> List.wrap()
|
||||||
|
|> Enum.concat(List.wrap(Application.get_env(:ash, :tracer)))
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Wether or not to surface errors to the root of the response"
|
@doc "Wether or not to surface errors to the root of the response"
|
||||||
|
|
|
@ -9,6 +9,110 @@ defmodule AshGraphql.Graphql.Resolver do
|
||||||
def resolve(%Absinthe.Resolution{state: :resolved} = resolution, _),
|
def resolve(%Absinthe.Resolution{state: :resolved} = resolution, _),
|
||||||
do: resolution
|
do: resolution
|
||||||
|
|
||||||
|
def resolve(
|
||||||
|
%{arguments: arguments, context: context} = resolution,
|
||||||
|
{api, resource, %{name: query_name, action: action}}
|
||||||
|
) do
|
||||||
|
action = Ash.Resource.Info.action(resource, action)
|
||||||
|
|
||||||
|
case handle_arguments(resource, action, arguments) do
|
||||||
|
{:ok, arguments} ->
|
||||||
|
metadata = %{
|
||||||
|
api: api,
|
||||||
|
resource: resource,
|
||||||
|
resource_short_name: Ash.Resource.Info.short_name(resource),
|
||||||
|
actor: Map.get(context, :actor),
|
||||||
|
tenant: Map.get(context, :tenant),
|
||||||
|
action: action,
|
||||||
|
source: :graphql,
|
||||||
|
query: query_name,
|
||||||
|
authorize?: AshGraphql.Api.Info.authorize?(api)
|
||||||
|
}
|
||||||
|
|
||||||
|
trace api,
|
||||||
|
resource,
|
||||||
|
:gql_query,
|
||||||
|
query_name,
|
||||||
|
metadata do
|
||||||
|
result =
|
||||||
|
%Ash.ActionInput{api: api, resource: resource}
|
||||||
|
|> Ash.ActionInput.set_context(get_context(context))
|
||||||
|
|> Ash.ActionInput.for_action(action.name, arguments)
|
||||||
|
|> api.run_action()
|
||||||
|
|> case do
|
||||||
|
{:ok, result} ->
|
||||||
|
load_opts =
|
||||||
|
[
|
||||||
|
actor: Map.get(context, :actor),
|
||||||
|
action: action,
|
||||||
|
api: api,
|
||||||
|
verbose?: AshGraphql.Api.Info.debug?(api),
|
||||||
|
authorize?: AshGraphql.Api.Info.authorize?(api),
|
||||||
|
tenant: Map.get(context, :tenant)
|
||||||
|
]
|
||||||
|
|
||||||
|
if Ash.Type.can_load?(action.returns, action.constraints) do
|
||||||
|
{fields, path} = nested_fields_and_path(resolution, [], [])
|
||||||
|
|
||||||
|
loads =
|
||||||
|
type_loads(
|
||||||
|
fields,
|
||||||
|
action.returns,
|
||||||
|
action.constraints,
|
||||||
|
load_opts,
|
||||||
|
resource,
|
||||||
|
action.name,
|
||||||
|
resolution,
|
||||||
|
path,
|
||||||
|
hd(resolution.path),
|
||||||
|
nil
|
||||||
|
)
|
||||||
|
|
||||||
|
case loads do
|
||||||
|
[] ->
|
||||||
|
{:ok, result}
|
||||||
|
|
||||||
|
loads ->
|
||||||
|
Ash.Type.load(
|
||||||
|
action.returns,
|
||||||
|
result,
|
||||||
|
loads,
|
||||||
|
action.constraints,
|
||||||
|
Map.new(load_opts)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
{:ok, result}
|
||||||
|
end
|
||||||
|
|
||||||
|
{:error, error} ->
|
||||||
|
{:error, error}
|
||||||
|
end
|
||||||
|
|
||||||
|
resolution
|
||||||
|
|> Absinthe.Resolution.put_result(
|
||||||
|
to_resolution(
|
||||||
|
result,
|
||||||
|
context,
|
||||||
|
api
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|> add_root_errors(api, result)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:error, error} ->
|
||||||
|
{:error, error}
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
e ->
|
||||||
|
if AshGraphql.Api.Info.show_raised_errors?(api) do
|
||||||
|
error = Ash.Error.to_ash_error([e], __STACKTRACE__)
|
||||||
|
Absinthe.Resolution.put_result(resolution, to_resolution({:error, error}, context, api))
|
||||||
|
else
|
||||||
|
something_went_wrong(resolution, e, api, __STACKTRACE__)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def resolve(
|
def resolve(
|
||||||
%{arguments: arguments, context: context} = resolution,
|
%{arguments: arguments, context: context} = resolution,
|
||||||
{api, resource,
|
{api, resource,
|
||||||
|
@ -42,7 +146,9 @@ defmodule AshGraphql.Graphql.Resolver do
|
||||||
opts = [
|
opts = [
|
||||||
actor: Map.get(context, :actor),
|
actor: Map.get(context, :actor),
|
||||||
action: action,
|
action: action,
|
||||||
verbose?: AshGraphql.Api.Info.debug?(api)
|
verbose?: AshGraphql.Api.Info.debug?(api),
|
||||||
|
authorize?: AshGraphql.Api.Info.authorize?(api),
|
||||||
|
tenant: Map.get(context, :tenant)
|
||||||
]
|
]
|
||||||
|
|
||||||
filter = identity_filter(identity, resource, arguments)
|
filter = identity_filter(identity, resource, arguments)
|
||||||
|
@ -66,6 +172,7 @@ defmodule AshGraphql.Graphql.Resolver do
|
||||||
api: api,
|
api: api,
|
||||||
tenant: Map.get(context, :tenant),
|
tenant: Map.get(context, :tenant),
|
||||||
authorize?: AshGraphql.Api.Info.authorize?(api),
|
authorize?: AshGraphql.Api.Info.authorize?(api),
|
||||||
|
tracer: AshGraphql.Api.Info.tracer(api),
|
||||||
actor: Map.get(context, :actor)
|
actor: Map.get(context, :actor)
|
||||||
],
|
],
|
||||||
resource,
|
resource,
|
||||||
|
@ -96,6 +203,7 @@ defmodule AshGraphql.Graphql.Resolver do
|
||||||
api: api,
|
api: api,
|
||||||
tenant: Map.get(context, :tenant),
|
tenant: Map.get(context, :tenant),
|
||||||
authorize?: AshGraphql.Api.Info.authorize?(api),
|
authorize?: AshGraphql.Api.Info.authorize?(api),
|
||||||
|
tracer: AshGraphql.Api.Info.tracer(api),
|
||||||
actor: Map.get(context, :actor)
|
actor: Map.get(context, :actor)
|
||||||
],
|
],
|
||||||
resource,
|
resource,
|
||||||
|
@ -174,7 +282,9 @@ defmodule AshGraphql.Graphql.Resolver do
|
||||||
opts = [
|
opts = [
|
||||||
actor: Map.get(context, :actor),
|
actor: Map.get(context, :actor),
|
||||||
action: action,
|
action: action,
|
||||||
verbose?: AshGraphql.Api.Info.debug?(api)
|
verbose?: AshGraphql.Api.Info.debug?(api),
|
||||||
|
authorize?: AshGraphql.Api.Info.authorize?(api),
|
||||||
|
tenant: Map.get(context, :tenant)
|
||||||
]
|
]
|
||||||
|
|
||||||
query =
|
query =
|
||||||
|
@ -188,6 +298,7 @@ defmodule AshGraphql.Graphql.Resolver do
|
||||||
api: api,
|
api: api,
|
||||||
tenant: Map.get(context, :tenant),
|
tenant: Map.get(context, :tenant),
|
||||||
authorize?: AshGraphql.Api.Info.authorize?(api),
|
authorize?: AshGraphql.Api.Info.authorize?(api),
|
||||||
|
tracer: AshGraphql.Api.Info.tracer(api),
|
||||||
actor: Map.get(context, :actor)
|
actor: Map.get(context, :actor)
|
||||||
],
|
],
|
||||||
resource,
|
resource,
|
||||||
|
@ -263,7 +374,9 @@ defmodule AshGraphql.Graphql.Resolver do
|
||||||
opts = [
|
opts = [
|
||||||
actor: Map.get(context, :actor),
|
actor: Map.get(context, :actor),
|
||||||
action: action,
|
action: action,
|
||||||
verbose?: AshGraphql.Api.Info.debug?(api)
|
verbose?: AshGraphql.Api.Info.debug?(api),
|
||||||
|
authorize?: AshGraphql.Api.Info.authorize?(api),
|
||||||
|
tenant: Map.get(context, :tenant)
|
||||||
]
|
]
|
||||||
|
|
||||||
pagination = Ash.Resource.Info.action(resource, action).pagination
|
pagination = Ash.Resource.Info.action(resource, action).pagination
|
||||||
|
@ -285,6 +398,7 @@ defmodule AshGraphql.Graphql.Resolver do
|
||||||
api: api,
|
api: api,
|
||||||
tenant: Map.get(context, :tenant),
|
tenant: Map.get(context, :tenant),
|
||||||
authorize?: AshGraphql.Api.Info.authorize?(api),
|
authorize?: AshGraphql.Api.Info.authorize?(api),
|
||||||
|
tracer: AshGraphql.Api.Info.tracer(api),
|
||||||
actor: Map.get(context, :actor)
|
actor: Map.get(context, :actor)
|
||||||
],
|
],
|
||||||
resource,
|
resource,
|
||||||
|
@ -880,6 +994,8 @@ defmodule AshGraphql.Graphql.Resolver do
|
||||||
actor: Map.get(context, :actor),
|
actor: Map.get(context, :actor),
|
||||||
action: action,
|
action: action,
|
||||||
verbose?: AshGraphql.Api.Info.debug?(api),
|
verbose?: AshGraphql.Api.Info.debug?(api),
|
||||||
|
authorize?: AshGraphql.Api.Info.authorize?(api),
|
||||||
|
tenant: Map.get(context, :tenant),
|
||||||
upsert?: upsert?
|
upsert?: upsert?
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -905,6 +1021,7 @@ defmodule AshGraphql.Graphql.Resolver do
|
||||||
api: api,
|
api: api,
|
||||||
tenant: Map.get(context, :tenant),
|
tenant: Map.get(context, :tenant),
|
||||||
authorize?: AshGraphql.Api.Info.authorize?(api),
|
authorize?: AshGraphql.Api.Info.authorize?(api),
|
||||||
|
tracer: AshGraphql.Api.Info.tracer(api),
|
||||||
actor: Map.get(context, :actor)
|
actor: Map.get(context, :actor)
|
||||||
],
|
],
|
||||||
resource,
|
resource,
|
||||||
|
@ -1028,7 +1145,9 @@ defmodule AshGraphql.Graphql.Resolver do
|
||||||
opts = [
|
opts = [
|
||||||
actor: Map.get(context, :actor),
|
actor: Map.get(context, :actor),
|
||||||
action: action,
|
action: action,
|
||||||
verbose?: AshGraphql.Api.Info.debug?(api)
|
verbose?: AshGraphql.Api.Info.debug?(api),
|
||||||
|
authorize?: AshGraphql.Api.Info.authorize?(api),
|
||||||
|
tenant: Map.get(context, :tenant)
|
||||||
]
|
]
|
||||||
|
|
||||||
changeset =
|
changeset =
|
||||||
|
@ -1046,6 +1165,7 @@ defmodule AshGraphql.Graphql.Resolver do
|
||||||
api: api,
|
api: api,
|
||||||
tenant: Map.get(context, :tenant),
|
tenant: Map.get(context, :tenant),
|
||||||
authorize?: AshGraphql.Api.Info.authorize?(api),
|
authorize?: AshGraphql.Api.Info.authorize?(api),
|
||||||
|
tracer: AshGraphql.Api.Info.tracer(api),
|
||||||
actor: Map.get(context, :actor)
|
actor: Map.get(context, :actor)
|
||||||
],
|
],
|
||||||
resource,
|
resource,
|
||||||
|
@ -1181,7 +1301,13 @@ defmodule AshGraphql.Graphql.Resolver do
|
||||||
|> add_root_errors(api, result)
|
|> add_root_errors(api, result)
|
||||||
|
|
||||||
{:ok, initial} ->
|
{:ok, initial} ->
|
||||||
opts = destroy_opts(api, context, action)
|
opts = [
|
||||||
|
action: action,
|
||||||
|
verbose?: AshGraphql.Api.Info.debug?(api),
|
||||||
|
actor: Map.get(context, :actor),
|
||||||
|
authorize?: AshGraphql.Api.Info.authorize?(api),
|
||||||
|
tenant: Map.get(context, :tenant)
|
||||||
|
]
|
||||||
|
|
||||||
changeset =
|
changeset =
|
||||||
initial
|
initial
|
||||||
|
@ -1264,9 +1390,7 @@ defmodule AshGraphql.Graphql.Resolver do
|
||||||
defp something_went_wrong(resolution, e, api, stacktrace) do
|
defp something_went_wrong(resolution, e, api, stacktrace) do
|
||||||
tracer = AshGraphql.Api.Info.tracer(api)
|
tracer = AshGraphql.Api.Info.tracer(api)
|
||||||
|
|
||||||
if tracer do
|
Ash.Tracer.set_error(tracer, e)
|
||||||
tracer.set_error(Ash.Error.to_ash_error(e))
|
|
||||||
end
|
|
||||||
|
|
||||||
uuid = log_exception(e, stacktrace)
|
uuid = log_exception(e, stacktrace)
|
||||||
|
|
||||||
|
@ -1703,7 +1827,17 @@ defmodule AshGraphql.Graphql.Resolver do
|
||||||
already_expanded?
|
already_expanded?
|
||||||
)
|
)
|
||||||
|
|
||||||
Ash.Type.embedded_type?(type) || Ash.Resource.Info.resource?(type) ->
|
Ash.Type.embedded_type?(type) || Ash.Resource.Info.resource?(type) ||
|
||||||
|
(type in [Ash.Type.Struct, :struct] && constraints[:instance_of] &&
|
||||||
|
(Ash.Type.embedded_type?(constraints[:instance_of]) ||
|
||||||
|
Ash.Resource.Info.resource?(constraints[:instance_of]))) ->
|
||||||
|
type =
|
||||||
|
if type in [:struct, Ash.Type.Struct] do
|
||||||
|
constraints[:instance_of]
|
||||||
|
else
|
||||||
|
type
|
||||||
|
end
|
||||||
|
|
||||||
fields =
|
fields =
|
||||||
if already_expanded? do
|
if already_expanded? do
|
||||||
selections
|
selections
|
||||||
|
@ -1723,8 +1857,7 @@ defmodule AshGraphql.Graphql.Resolver do
|
||||||
fields
|
fields
|
||||||
end
|
end
|
||||||
|
|
||||||
fields
|
resource_loads(fields, type, resolution, load_opts, path)
|
||||||
|> resource_loads(type, resolution, load_opts, path)
|
|
||||||
|
|
||||||
type == Ash.Type.Union ->
|
type == Ash.Type.Union ->
|
||||||
{global_selections, fragments} =
|
{global_selections, fragments} =
|
||||||
|
@ -1987,21 +2120,6 @@ defmodule AshGraphql.Graphql.Resolver do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp destroy_opts(api, context, action) do
|
|
||||||
if AshGraphql.Api.Info.authorize?(api) do
|
|
||||||
[
|
|
||||||
actor: Map.get(context, :actor),
|
|
||||||
action: action,
|
|
||||||
verbose?: AshGraphql.Api.Info.debug?(api)
|
|
||||||
]
|
|
||||||
else
|
|
||||||
[
|
|
||||||
action: action,
|
|
||||||
verbose?: AshGraphql.Api.Info.debug?(api)
|
|
||||||
]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp add_root_errors(resolution, api, {:error, error_or_errors}) do
|
defp add_root_errors(resolution, api, {:error, error_or_errors}) do
|
||||||
do_root_errors(api, resolution, error_or_errors)
|
do_root_errors(api, resolution, error_or_errors)
|
||||||
end
|
end
|
||||||
|
|
|
@ -24,43 +24,24 @@ defmodule AshGraphql.Resource.ManagedRelationship do
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
doc: """
|
doc: """
|
||||||
If the managed_relationship has `on_lookup` behavior, this option determines whether or not the primary key is provided in the input object for looking up.
|
If the managed_relationship has `on_lookup` behavior, this option determines whether or not the primary key is provided in the input object for looking up.
|
||||||
|
|
||||||
This option is ignored if there is no `on_lookup`.
|
|
||||||
"""
|
"""
|
||||||
],
|
],
|
||||||
lookup_identities: [
|
lookup_identities: [
|
||||||
type: {:list, :atom},
|
type: {:list, :atom},
|
||||||
doc: """
|
doc: """
|
||||||
If the managed_relationship has `on_lookup` behavior, this option determines which identities are provided in the input object for looking up.
|
Determines which identities are provided in the input object for looking up, if there is `on_lookup` behavior. Defalts to the `use_identities` option.
|
||||||
|
|
||||||
This option is ignored if there is no `on_lookup`. By default *all* identities are provided.
|
|
||||||
"""
|
"""
|
||||||
],
|
],
|
||||||
type_name: [
|
type_name: [
|
||||||
type: :atom,
|
type: :atom,
|
||||||
doc: """
|
doc: """
|
||||||
The name of the input object that will be derived. Defaults to `<action_type>_<resource>_<argument_name>_input`
|
The name of the input object that will be derived. Defaults to `<action_type>_<resource>_<argument_name>_input`
|
||||||
|
|
||||||
Because multiple actions could potentially be managing the same relationship, it isn't suficcient to
|
|
||||||
default to something like `<resource>_<relationship>_input`. Additionally, Ash doesn't expose resource
|
|
||||||
action names by default, meaning that there is no automatic way to ensure that all
|
|
||||||
of these have a default name that will always be unique. If you have multiple actions of the same
|
|
||||||
type that manage a relationship with an argument of the same name, you will get a compile-time error.
|
|
||||||
"""
|
"""
|
||||||
],
|
],
|
||||||
types: [
|
types: [
|
||||||
type: :any,
|
type: :any,
|
||||||
doc: """
|
doc: """
|
||||||
A keyword list of field names to their graphql type identifiers.
|
A keyword list of field names to their graphql type identifiers.
|
||||||
|
|
||||||
Since managed relationships can ultimately call multiple actions, there is the possibility
|
|
||||||
of field type conflicts. Use this to determine the type of fields and remove the conflict warnings.
|
|
||||||
|
|
||||||
For `non_null` use `{:non_null, type}`, and for a list, use `{:array, type}`, for example:
|
|
||||||
|
|
||||||
`{:non_null, {:array, {:non_null, :string}}}` for a non null list of non null strings.
|
|
||||||
|
|
||||||
To *remove* a key from the input object, simply pass `nil` as the type.
|
|
||||||
"""
|
"""
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
|
@ -35,9 +35,7 @@ defmodule AshGraphql.Resource.Mutation do
|
||||||
modify_resolution: [
|
modify_resolution: [
|
||||||
type: :mfa,
|
type: :mfa,
|
||||||
doc: """
|
doc: """
|
||||||
An MFA that will be called with the resolution, the changeset, and the result of the action as the first three arguments (followed by the arguments in the mfa).
|
An MFA that will be called with the resolution, the query, and the result of the action as the first three arguments. See the [the guide](/documentation/topics/modifying-the-resolution.html) for more.
|
||||||
Must return a new absinthe resolution. This can be used to implement things like setting cookies based on resource actions. A method of using resolution context
|
|
||||||
for that is documented here: https://hexdocs.pm/absinthe_plug/Absinthe.Plug.html#module-before-send
|
|
||||||
"""
|
"""
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -56,9 +54,7 @@ defmodule AshGraphql.Resource.Mutation do
|
||||||
identity: [
|
identity: [
|
||||||
type: :atom,
|
type: :atom,
|
||||||
doc: """
|
doc: """
|
||||||
The identity to use to fetch the record to be updated.
|
The identity to use to fetch the record to be updated. Use `false` if no identity is required.
|
||||||
|
|
||||||
If no identity is required (e.g for a read action that already knows how to fetch the item to be updated), use `false`.
|
|
||||||
"""
|
"""
|
||||||
],
|
],
|
||||||
read_action: [
|
read_action: [
|
||||||
|
@ -87,8 +83,7 @@ defmodule AshGraphql.Resource.Mutation do
|
||||||
identity: [
|
identity: [
|
||||||
type: :atom,
|
type: :atom,
|
||||||
doc: """
|
doc: """
|
||||||
The identity to use to fetch the record to be destroyed.
|
The identity to use to fetch the record to be destroyed. Use `false` if no identity is required.
|
||||||
If no identity is required (e.g for a read action that already knows how to fetch the item to be updated), use `false`.
|
|
||||||
"""
|
"""
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
|
@ -29,11 +29,7 @@ defmodule AshGraphql.Resource.Query do
|
||||||
type_name: [
|
type_name: [
|
||||||
type: :atom,
|
type: :atom,
|
||||||
doc: """
|
doc: """
|
||||||
Override the type name returned by this query. Must be set if the read action has `metadata`.
|
Override the type name returned by this query. Must be set if the read action has `metadata` that is not hidden via the `show_metadata` key.
|
||||||
|
|
||||||
To ignore any action metadata, set this to the same type the resource uses, or set `show_metadata` to `[]`.
|
|
||||||
To show metadata in the response, choose a new name here, like `:user_with_token` to get a response type that
|
|
||||||
includes the additional fields.
|
|
||||||
"""
|
"""
|
||||||
],
|
],
|
||||||
metadata_names: [
|
metadata_names: [
|
||||||
|
@ -54,13 +50,7 @@ defmodule AshGraphql.Resource.Query do
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
default: false,
|
default: false,
|
||||||
doc: """
|
doc: """
|
||||||
Places the query in the `mutations` key instead. The use cases for this are likely very minimal.
|
Places the query in the `mutations` key instead. Not typically necessary, but is often paired with `as_mutation?`. See the [the guide](/documentation/topics/modifying-the-resolution.html) for more.
|
||||||
|
|
||||||
If you have a query that needs to modify the graphql context using `modify_resolution`, then you
|
|
||||||
should likely set this as well. A simple example might be a `log_in`, which could be a read
|
|
||||||
action on the user that accepts an email/password, and should then set some context in the graphql
|
|
||||||
inside of `modify_resolution`. Once in the context, you can see the guide referenced in `modify_resolution`
|
|
||||||
for more on setting the session or a cookie with an auth token.
|
|
||||||
"""
|
"""
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -80,12 +70,7 @@ defmodule AshGraphql.Resource.Query do
|
||||||
modify_resolution: [
|
modify_resolution: [
|
||||||
type: :mfa,
|
type: :mfa,
|
||||||
doc: """
|
doc: """
|
||||||
An MFA that will be called with the resolution, the query, and the result of the action as the first three arguments (followed by the arguments in the mfa).
|
An MFA that will be called with the resolution, the query, and the result of the action as the first three arguments. See the [the guide](/documentation/topics/modifying-the-resolution.html) for more.
|
||||||
Must return a new absinthe resolution. This can be used to implement things like setting cookies based on resource actions. A method of using resolution context
|
|
||||||
for that is documented here: https://hexdocs.pm/absinthe_plug/Absinthe.Plug.html#module-before-send
|
|
||||||
|
|
||||||
*Important* if you are modifying the context, then you should also set `as_mutation?` to true and represent
|
|
||||||
this in your graphql as a mutation. See `as_mutation?` for more.
|
|
||||||
"""
|
"""
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -105,12 +90,7 @@ defmodule AshGraphql.Resource.Query do
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
default: false,
|
default: false,
|
||||||
doc: """
|
doc: """
|
||||||
If true, the graphql queries/resolvers for this resource will be built to honor the [relay specification](https://relay.dev/graphql/connections.htm).
|
If true, the graphql queries/resolvers for this resource will be built to honor the relay specification. See [the relay guide](/documentation/topics/relay.html) for more.
|
||||||
|
|
||||||
The two changes that are made currently are:
|
|
||||||
|
|
||||||
* the type for the resource will implement the `Node` interface
|
|
||||||
* pagination over that resource will behave as a Connection.
|
|
||||||
"""
|
"""
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
|
@ -47,6 +47,38 @@ defmodule AshGraphql.Resource do
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action_schema [
|
||||||
|
name: [
|
||||||
|
type: :atom,
|
||||||
|
doc: "The name to use for the query.",
|
||||||
|
default: :get
|
||||||
|
],
|
||||||
|
action: [
|
||||||
|
type: :atom,
|
||||||
|
doc: "The action to use for the query.",
|
||||||
|
required: true
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
defmodule Action do
|
||||||
|
@moduledoc "Represents a configured generic action"
|
||||||
|
defstruct [:type, :name, :action]
|
||||||
|
end
|
||||||
|
|
||||||
|
@action %Spark.Dsl.Entity{
|
||||||
|
name: :action,
|
||||||
|
schema: @action_schema,
|
||||||
|
args: [:name, :action],
|
||||||
|
describe: "Runs a generic action",
|
||||||
|
examples: [
|
||||||
|
"action :check_status, :check_status"
|
||||||
|
],
|
||||||
|
target: Action,
|
||||||
|
auto_set_fields: [
|
||||||
|
type: :action
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
@create %Spark.Dsl.Entity{
|
@create %Spark.Dsl.Entity{
|
||||||
name: :create,
|
name: :create,
|
||||||
schema: Mutation.create_schema(),
|
schema: Mutation.create_schema(),
|
||||||
|
@ -106,7 +138,8 @@ defmodule AshGraphql.Resource do
|
||||||
entities: [
|
entities: [
|
||||||
@get,
|
@get,
|
||||||
@read_one,
|
@read_one,
|
||||||
@list
|
@list,
|
||||||
|
@action
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,6 +184,15 @@ defmodule AshGraphql.Resource do
|
||||||
1.) provide the `:types` option to the `managed_relationship` constructor (see that option for more)
|
1.) provide the `:types` option to the `managed_relationship` constructor (see that option for more)
|
||||||
2.) define a custom type, with a custom input object (see the custom types guide), and use that custom type instead of `:map`
|
2.) define a custom type, with a custom input object (see the custom types guide), and use that custom type instead of `:map`
|
||||||
3.) change your actions to not have overlapping inputs with different types
|
3.) change your actions to not have overlapping inputs with different types
|
||||||
|
|
||||||
|
Since managed relationships can ultimately call multiple actions, there is the possibility
|
||||||
|
of field type conflicts. Use the `types` option to determine the type of fields and remove the conflict warnings.
|
||||||
|
|
||||||
|
For `non_null` use `{:non_null, type}`, and for a list, use `{:array, type}`, for example:
|
||||||
|
|
||||||
|
`{:non_null, {:array, {:non_null, :string}}}` for a non null list of non null strings.
|
||||||
|
|
||||||
|
To *remove* a key from the input object, simply pass `nil` as the type.
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,7 +237,8 @@ defmodule AshGraphql.Resource do
|
||||||
entities: [
|
entities: [
|
||||||
@create,
|
@create,
|
||||||
@update,
|
@update,
|
||||||
@destroy
|
@destroy,
|
||||||
|
@action
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,10 +314,7 @@ defmodule AshGraphql.Resource do
|
||||||
keyset_field: [
|
keyset_field: [
|
||||||
type: :atom,
|
type: :atom,
|
||||||
doc: """
|
doc: """
|
||||||
If set, the keyset will be displayed on all read actions in this field.
|
If set, the keyset will be displayed on all read actions in this field. It will be `nil` unless at least one of the read actions on a resource uses keyset pagination or it is the result of a mutation
|
||||||
|
|
||||||
It will always be `nil` unless at least one of the read actions on a resource uses keyset pagination.
|
|
||||||
It will also be nil on any mutation results.
|
|
||||||
"""
|
"""
|
||||||
],
|
],
|
||||||
attribute_types: [
|
attribute_types: [
|
||||||
|
@ -335,19 +375,6 @@ defmodule AshGraphql.Resource do
|
||||||
|
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
This Ash resource extension adds configuration for exposing a resource in a graphql.
|
This Ash resource extension adds configuration for exposing a resource in a graphql.
|
||||||
|
|
||||||
<!--- ash-hq-hide-start --> <!--- -->
|
|
||||||
|
|
||||||
## DSL Documentation
|
|
||||||
|
|
||||||
### Index
|
|
||||||
|
|
||||||
#{Spark.Dsl.Extension.doc_index(@sections)}
|
|
||||||
|
|
||||||
### Docs
|
|
||||||
|
|
||||||
#{Spark.Dsl.Extension.doc(@sections)}
|
|
||||||
<!--- ash-hq-hide-stop --> <!--- -->
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use Spark.Dsl.Extension, sections: @sections, transformers: @transformers, verifiers: @verifiers
|
use Spark.Dsl.Extension, sections: @sections, transformers: @transformers, verifiers: @verifiers
|
||||||
|
@ -415,27 +442,49 @@ defmodule AshGraphql.Resource do
|
||||||
if type do
|
if type do
|
||||||
resource
|
resource
|
||||||
|> queries()
|
|> queries()
|
||||||
|> Enum.filter(&(&1.as_mutation? == as_mutations?))
|
|> Enum.filter(&(Map.get(&1, :as_mutation?, false) == as_mutations?))
|
||||||
|> Enum.map(fn query ->
|
|> Enum.map(fn
|
||||||
query_action =
|
%{type: :action, name: name, action: action} = query ->
|
||||||
Ash.Resource.Info.action(resource, query.action) ||
|
query_action =
|
||||||
raise "No such action #{query.action} on #{resource}"
|
Ash.Resource.Info.action(resource, action) ||
|
||||||
|
raise "No such action #{action} on #{resource}"
|
||||||
|
|
||||||
%Absinthe.Blueprint.Schema.FieldDefinition{
|
%Absinthe.Blueprint.Schema.FieldDefinition{
|
||||||
arguments: args(query.type, resource, query_action, schema, query.identity),
|
arguments: generic_action_args(query_action, resource, schema),
|
||||||
identifier: query.name,
|
identifier: name,
|
||||||
middleware:
|
middleware:
|
||||||
action_middleware ++
|
action_middleware ++
|
||||||
[
|
[
|
||||||
{{AshGraphql.Graphql.Resolver, :resolve}, {api, resource, query}}
|
{{AshGraphql.Graphql.Resolver, :resolve}, {api, resource, query}}
|
||||||
],
|
],
|
||||||
complexity: {AshGraphql.Graphql.Resolver, :query_complexity},
|
complexity: {AshGraphql.Graphql.Resolver, :query_complexity},
|
||||||
module: schema,
|
module: schema,
|
||||||
name: to_string(query.name),
|
name: to_string(name),
|
||||||
description: Ash.Resource.Info.action(resource, query.action).description,
|
description: query_action.description,
|
||||||
type: query_type(query, resource, query_action, type),
|
type: generic_action_type(query_action, resource),
|
||||||
__reference__: ref(__ENV__)
|
__reference__: ref(__ENV__)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query ->
|
||||||
|
query_action =
|
||||||
|
Ash.Resource.Info.action(resource, query.action) ||
|
||||||
|
raise "No such action #{query.action} on #{resource}"
|
||||||
|
|
||||||
|
%Absinthe.Blueprint.Schema.FieldDefinition{
|
||||||
|
arguments: args(query.type, resource, query_action, schema, query.identity),
|
||||||
|
identifier: query.name,
|
||||||
|
middleware:
|
||||||
|
action_middleware ++
|
||||||
|
[
|
||||||
|
{{AshGraphql.Graphql.Resolver, :resolve}, {api, resource, query}}
|
||||||
|
],
|
||||||
|
complexity: {AshGraphql.Graphql.Resolver, :query_complexity},
|
||||||
|
module: schema,
|
||||||
|
name: to_string(query.name),
|
||||||
|
description: Ash.Resource.Info.action(resource, query.action).description,
|
||||||
|
type: query_type(query, resource, query_action, type),
|
||||||
|
__reference__: ref(__ENV__)
|
||||||
|
}
|
||||||
end)
|
end)
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
|
@ -448,6 +497,27 @@ defmodule AshGraphql.Resource do
|
||||||
resource
|
resource
|
||||||
|> mutations()
|
|> mutations()
|
||||||
|> Enum.map(fn
|
|> Enum.map(fn
|
||||||
|
%{type: :action, name: name, action: action} = query ->
|
||||||
|
query_action =
|
||||||
|
Ash.Resource.Info.action(resource, action) ||
|
||||||
|
raise "No such action #{action} on #{resource}"
|
||||||
|
|
||||||
|
%Absinthe.Blueprint.Schema.FieldDefinition{
|
||||||
|
arguments: generic_action_args(query_action, resource, schema),
|
||||||
|
identifier: name,
|
||||||
|
middleware:
|
||||||
|
action_middleware ++
|
||||||
|
[
|
||||||
|
{{AshGraphql.Graphql.Resolver, :resolve}, {api, resource, query}}
|
||||||
|
],
|
||||||
|
complexity: {AshGraphql.Graphql.Resolver, :query_complexity},
|
||||||
|
module: schema,
|
||||||
|
name: to_string(name),
|
||||||
|
description: query_action.description,
|
||||||
|
type: generic_action_type(query_action, resource),
|
||||||
|
__reference__: ref(__ENV__)
|
||||||
|
}
|
||||||
|
|
||||||
%{type: :destroy} = mutation ->
|
%{type: :destroy} = mutation ->
|
||||||
action =
|
action =
|
||||||
Ash.Resource.Info.action(resource, mutation.action) ||
|
Ash.Resource.Info.action(resource, mutation.action) ||
|
||||||
|
@ -836,39 +906,44 @@ defmodule AshGraphql.Resource do
|
||||||
argument_names = AshGraphql.Resource.Info.argument_names(resource)
|
argument_names = AshGraphql.Resource.Info.argument_names(resource)
|
||||||
|
|
||||||
attribute_fields =
|
attribute_fields =
|
||||||
if action.type == :destroy && !action.soft? do
|
cond do
|
||||||
[]
|
action.type == :action ->
|
||||||
else
|
[]
|
||||||
resource
|
|
||||||
|> Ash.Resource.Info.public_attributes()
|
|
||||||
|> Enum.filter(fn attribute ->
|
|
||||||
AshGraphql.Resource.Info.show_field?(resource, attribute.name) &&
|
|
||||||
(is_nil(action.accept) || attribute.name in action.accept) && attribute.writable?
|
|
||||||
end)
|
|
||||||
|> Enum.map(fn attribute ->
|
|
||||||
allow_nil? =
|
|
||||||
attribute.allow_nil? || attribute.default != nil || type == :update ||
|
|
||||||
attribute.generated? ||
|
|
||||||
(type == :create && attribute.name in action.allow_nil_input)
|
|
||||||
|
|
||||||
explicitly_required = attribute.name in action.require_attributes
|
action.type == :destroy && !action.soft? ->
|
||||||
|
[]
|
||||||
|
|
||||||
field_type =
|
true ->
|
||||||
attribute.type
|
resource
|
||||||
|> field_type(attribute, resource, true)
|
|> Ash.Resource.Info.public_attributes()
|
||||||
|> maybe_wrap_non_null(explicitly_required || not allow_nil?)
|
|> Enum.filter(fn attribute ->
|
||||||
|
AshGraphql.Resource.Info.show_field?(resource, attribute.name) &&
|
||||||
|
(is_nil(action.accept) || attribute.name in action.accept) && attribute.writable?
|
||||||
|
end)
|
||||||
|
|> Enum.map(fn attribute ->
|
||||||
|
allow_nil? =
|
||||||
|
attribute.allow_nil? || attribute.default != nil || type == :update ||
|
||||||
|
attribute.generated? ||
|
||||||
|
(type == :create && attribute.name in action.allow_nil_input)
|
||||||
|
|
||||||
name = field_names[attribute.name] || attribute.name
|
explicitly_required = attribute.name in action.require_attributes
|
||||||
|
|
||||||
%Absinthe.Blueprint.Schema.FieldDefinition{
|
field_type =
|
||||||
description: attribute.description,
|
attribute.type
|
||||||
identifier: attribute.name,
|
|> field_type(attribute, resource, true)
|
||||||
module: schema,
|
|> maybe_wrap_non_null(explicitly_required || not allow_nil?)
|
||||||
name: to_string(name),
|
|
||||||
type: field_type,
|
name = field_names[attribute.name] || attribute.name
|
||||||
__reference__: ref(__ENV__)
|
|
||||||
}
|
%Absinthe.Blueprint.Schema.FieldDefinition{
|
||||||
end)
|
description: attribute.description,
|
||||||
|
identifier: attribute.name,
|
||||||
|
module: schema,
|
||||||
|
name: to_string(name),
|
||||||
|
type: field_type,
|
||||||
|
__reference__: ref(__ENV__)
|
||||||
|
}
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
argument_fields =
|
argument_fields =
|
||||||
|
@ -1096,6 +1171,39 @@ defmodule AshGraphql.Resource do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp generic_action_type(action, resource) do
|
||||||
|
fake_attribute = %{
|
||||||
|
type: action.returns,
|
||||||
|
constraints: action.constraints,
|
||||||
|
allow_nil?: Map.get(action, :allow_nil?, false),
|
||||||
|
name: action.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fake_attribute.type
|
||||||
|
|> field_type(fake_attribute, resource, false)
|
||||||
|
|> maybe_wrap_non_null(argument_required?(fake_attribute))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp generic_action_args(action, resource, schema) do
|
||||||
|
action.arguments
|
||||||
|
|> Enum.reject(& &1.private?)
|
||||||
|
|> Enum.map(fn argument ->
|
||||||
|
type =
|
||||||
|
argument.type
|
||||||
|
|> field_type(argument, resource, true)
|
||||||
|
|> maybe_wrap_non_null(argument_required?(argument))
|
||||||
|
|
||||||
|
%Absinthe.Blueprint.Schema.FieldDefinition{
|
||||||
|
identifier: argument.name,
|
||||||
|
module: schema,
|
||||||
|
name: to_string(argument.name),
|
||||||
|
description: argument.description,
|
||||||
|
type: type,
|
||||||
|
__reference__: ref(__ENV__)
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
defp args(action_type, resource, action, schema, identity \\ nil)
|
defp args(action_type, resource, action, schema, identity \\ nil)
|
||||||
|
|
||||||
defp args(:get, resource, action, schema, nil) do
|
defp args(:get, resource, action, schema, nil) do
|
||||||
|
@ -1791,8 +1899,11 @@ defmodule AshGraphql.Resource do
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|> Enum.filter(fn identity ->
|
|> Enum.filter(fn identity ->
|
||||||
is_nil(managed_relationship.lookup_identities) ||
|
if is_nil(managed_relationship.lookup_identities) do
|
||||||
|
identity.name in List.wrap(opts[:use_identities])
|
||||||
|
else
|
||||||
identity.name in managed_relationship.lookup_identities
|
identity.name in managed_relationship.lookup_identities
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|> Enum.flat_map(fn identity ->
|
|> Enum.flat_map(fn identity ->
|
||||||
identity
|
identity
|
||||||
|
@ -2900,14 +3011,18 @@ defmodule AshGraphql.Resource do
|
||||||
relay? =
|
relay? =
|
||||||
resource
|
resource
|
||||||
|> queries()
|
|> queries()
|
||||||
|> Enum.any?(& &1.relay?)
|
|> Enum.any?(&Map.get(&1, :relay?))
|
||||||
|
|
||||||
countable? =
|
countable? =
|
||||||
resource
|
resource
|
||||||
|> queries()
|
|> queries()
|
||||||
|> Enum.any?(fn query ->
|
|> Enum.any?(fn
|
||||||
action = Ash.Resource.Info.action(resource, query.action)
|
%{relay?: true} = query ->
|
||||||
query.relay? && action.pagination && action.pagination.countable
|
action = Ash.Resource.Info.action(resource, query.action)
|
||||||
|
action.pagination && action.pagination.countable
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
end)
|
end)
|
||||||
|
|
||||||
if relay? do
|
if relay? do
|
||||||
|
@ -3119,7 +3234,7 @@ defmodule AshGraphql.Resource do
|
||||||
|
|
||||||
resource
|
resource
|
||||||
|> AshGraphql.Resource.Info.queries()
|
|> AshGraphql.Resource.Info.queries()
|
||||||
|> Enum.filter(&(&1.type_name && &1.type_name != resource_type))
|
|> Enum.filter(&(Map.get(&1, :type_name) && &1.type_name != resource_type))
|
||||||
|> Enum.map(fn query ->
|
|> Enum.map(fn query ->
|
||||||
relay? = Map.get(query, :relay?)
|
relay? = Map.get(query, :relay?)
|
||||||
|
|
||||||
|
@ -3161,7 +3276,7 @@ defmodule AshGraphql.Resource do
|
||||||
relay? =
|
relay? =
|
||||||
resource
|
resource
|
||||||
|> queries()
|
|> queries()
|
||||||
|> Enum.any?(& &1.relay?)
|
|> Enum.any?(&Map.get(&1, :relay?))
|
||||||
|
|
||||||
interfaces =
|
interfaces =
|
||||||
if relay? do
|
if relay? do
|
||||||
|
@ -3830,6 +3945,16 @@ defmodule AshGraphql.Resource do
|
||||||
defp get_specific_field_type(Ash.Type.UUID, _, _, _), do: :id
|
defp get_specific_field_type(Ash.Type.UUID, _, _, _), do: :id
|
||||||
defp get_specific_field_type(Ash.Type.Float, _, _, _), do: :float
|
defp get_specific_field_type(Ash.Type.Float, _, _, _), do: :float
|
||||||
|
|
||||||
|
defp get_specific_field_type(Ash.Type.Struct, %{constraints: constraints}, resource, input?) do
|
||||||
|
type =
|
||||||
|
if !input? && constraints[:instance_of] &&
|
||||||
|
Ash.Resource.Info.resource?(constraints[:instance_of]) do
|
||||||
|
AshGraphql.Resource.Info.type(constraints[:instance_of])
|
||||||
|
end
|
||||||
|
|
||||||
|
type || get_specific_field_type(Ash.Type.Map, %{constraints: constraints}, resource, input?)
|
||||||
|
end
|
||||||
|
|
||||||
defp get_specific_field_type(type, attribute, resource, _) do
|
defp get_specific_field_type(type, attribute, resource, _) do
|
||||||
raise """
|
raise """
|
||||||
Could not determine graphql field type for #{inspect(type)} on #{inspect(resource)}.#{attribute.name}
|
Could not determine graphql field type for #{inspect(type)} on #{inspect(resource)}.#{attribute.name}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
defmodule AshGraphql.Resource.Transformers.AddUnionTypeResolvers do
|
defmodule AshGraphql.Resource.Transformers.AddUnionTypeResolvers do
|
||||||
@moduledoc "Set the computation of resolving union types as functions"
|
# Set the computation of resolving union types as functions
|
||||||
|
@moduledoc false
|
||||||
use Spark.Dsl.Transformer
|
use Spark.Dsl.Transformer
|
||||||
|
|
||||||
def after?(_), do: true
|
def after?(_), do: true
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
defmodule AshGraphql.Resource.Transformers.RequireKeysetForRelayQueries do
|
defmodule AshGraphql.Resource.Transformers.RequireKeysetForRelayQueries do
|
||||||
@moduledoc "Ensures that all relay queries configure keyset pagination"
|
# Ensures that all relay queries configure keyset pagination
|
||||||
use Spark.Dsl.Transformer
|
@moduledoc false
|
||||||
|
|
||||||
|
use Spark.Dsl.Transformer
|
||||||
alias Spark.Dsl.Transformer
|
alias Spark.Dsl.Transformer
|
||||||
|
|
||||||
def after_compile?, do: true
|
def after_compile?, do: true
|
||||||
|
@ -10,7 +11,7 @@ defmodule AshGraphql.Resource.Transformers.RequireKeysetForRelayQueries do
|
||||||
dsl
|
dsl
|
||||||
|> AshGraphql.Resource.Info.queries()
|
|> AshGraphql.Resource.Info.queries()
|
||||||
|> Enum.each(fn query ->
|
|> Enum.each(fn query ->
|
||||||
if query.relay? do
|
if Map.get(query, :relay?) do
|
||||||
action = Ash.Resource.Info.action(dsl, query.action)
|
action = Ash.Resource.Info.action(dsl, query.action)
|
||||||
|
|
||||||
unless action.pagination && action.pagination.keyset? do
|
unless action.pagination && action.pagination.keyset? do
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
defmodule AshGraphql.Resource.Transformers.RequirePkeyDelimiter do
|
defmodule AshGraphql.Resource.Transformers.RequirePkeyDelimiter do
|
||||||
@moduledoc "Ensures that the resource has a primary key called `id`"
|
# Ensures that the resource has a primary key called `id`
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
use Spark.Dsl.Transformer
|
use Spark.Dsl.Transformer
|
||||||
|
|
||||||
alias Spark.Dsl.Transformer
|
alias Spark.Dsl.Transformer
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
defmodule AshGraphql.Resource.Transformers.ValidateActions do
|
defmodule AshGraphql.Resource.Transformers.ValidateActions do
|
||||||
@moduledoc "Ensures that all referenced actiosn exist"
|
# Ensures that all referenced actiosn exist
|
||||||
|
@moduledoc false
|
||||||
use Spark.Dsl.Transformer
|
use Spark.Dsl.Transformer
|
||||||
|
|
||||||
alias Spark.Dsl.Transformer
|
alias Spark.Dsl.Transformer
|
||||||
|
@ -16,12 +17,24 @@ defmodule AshGraphql.Resource.Transformers.ValidateActions do
|
||||||
%AshGraphql.Resource.Query{} ->
|
%AshGraphql.Resource.Query{} ->
|
||||||
:read
|
:read
|
||||||
|
|
||||||
|
%AshGraphql.Resource.Action{} ->
|
||||||
|
nil
|
||||||
|
|
||||||
%AshGraphql.Resource.Mutation{type: type} ->
|
%AshGraphql.Resource.Mutation{type: type} ->
|
||||||
type
|
type
|
||||||
end
|
end
|
||||||
|
|
||||||
available_actions = Transformer.get_entities(dsl, [:actions]) || []
|
available_actions = Transformer.get_entities(dsl, [:actions]) || []
|
||||||
|
|
||||||
|
available_actions =
|
||||||
|
if type do
|
||||||
|
Enum.filter(available_actions, fn action ->
|
||||||
|
action.type == type
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
available_actions
|
||||||
|
end
|
||||||
|
|
||||||
action =
|
action =
|
||||||
Enum.find(available_actions, fn action ->
|
Enum.find(available_actions, fn action ->
|
||||||
action.name == query_or_mutation.action
|
action.name == query_or_mutation.action
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
defmodule AshGraphql.Resource.Transformers.ValidateCompatibleNames do
|
defmodule AshGraphql.Resource.Transformers.ValidateCompatibleNames do
|
||||||
@moduledoc "Ensures that all field names are valid or remapped to something valid exist"
|
# Ensures that all field names are valid or remapped to something valid exist
|
||||||
|
@moduledoc false
|
||||||
use Spark.Dsl.Transformer
|
use Spark.Dsl.Transformer
|
||||||
|
|
||||||
alias Spark.Dsl.Transformer
|
alias Spark.Dsl.Transformer
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
defmodule AshGraphql.Resource.Verifiers.VerifyQueryMetadata do
|
defmodule AshGraphql.Resource.Verifiers.VerifyQueryMetadata do
|
||||||
@moduledoc "Ensures that queries for actions with metadata have a type set"
|
# Ensures that queries for actions with metadata have a type set
|
||||||
|
@moduledoc false
|
||||||
use Spark.Dsl.Verifier
|
use Spark.Dsl.Verifier
|
||||||
|
|
||||||
alias Spark.Dsl.Transformer
|
alias Spark.Dsl.Transformer
|
||||||
|
@ -7,6 +8,7 @@ defmodule AshGraphql.Resource.Verifiers.VerifyQueryMetadata do
|
||||||
def verify(dsl) do
|
def verify(dsl) do
|
||||||
dsl
|
dsl
|
||||||
|> AshGraphql.Resource.Info.queries()
|
|> AshGraphql.Resource.Info.queries()
|
||||||
|
|> Enum.reject(&(&1.type == :action))
|
||||||
|> Enum.each(fn query ->
|
|> Enum.each(fn query ->
|
||||||
action = Ash.Resource.Info.action(dsl, query.action)
|
action = Ash.Resource.Info.action(dsl, query.action)
|
||||||
show_metadata = query.show_metadata || Enum.map(Map.get(action, :metadata, []), & &1.name)
|
show_metadata = query.show_metadata || Enum.map(Map.get(action, :metadata, []), & &1.name)
|
||||||
|
|
71
mix.exs
71
mix.exs
|
@ -39,15 +39,16 @@ defmodule AshGraphql.MixProject do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp extras() do
|
defp extras() do
|
||||||
"documentation/**/*.md"
|
"documentation/**/*.{md,livemd,cheatmd}"
|
||||||
|> Path.wildcard()
|
|> Path.wildcard()
|
||||||
|> Enum.map(fn path ->
|
|> Enum.map(fn path ->
|
||||||
title =
|
title =
|
||||||
path
|
path
|
||||||
|> Path.basename(".md")
|
|> Path.basename(".md")
|
||||||
|
|> Path.basename(".livemd")
|
||||||
|
|> Path.basename(".cheatmd")
|
||||||
|> String.split(~r/[-_]/)
|
|> String.split(~r/[-_]/)
|
||||||
|> Enum.map(&String.capitalize/1)
|
|> Enum.map_join(" ", &capitalize/1)
|
||||||
|> Enum.join(" ")
|
|
||||||
|> case do
|
|> case do
|
||||||
"F A Q" ->
|
"F A Q" ->
|
||||||
"FAQ"
|
"FAQ"
|
||||||
|
@ -63,24 +64,29 @@ defmodule AshGraphql.MixProject do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp groups_for_extras() do
|
defp capitalize(string) do
|
||||||
"documentation/*"
|
string
|
||||||
|> Path.wildcard()
|
|> String.split(" ")
|
||||||
|> Enum.map(fn folder ->
|
|> Enum.map(fn string ->
|
||||||
name =
|
[hd | tail] = String.graphemes(string)
|
||||||
folder
|
String.capitalize(hd) <> Enum.join(tail)
|
||||||
|> Path.basename()
|
|
||||||
|> String.split(~r/[-_]/)
|
|
||||||
|> Enum.map(&String.capitalize/1)
|
|
||||||
|> Enum.join(" ")
|
|
||||||
|
|
||||||
{name, folder |> Path.join("**") |> Path.wildcard()}
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp groups_for_extras() do
|
||||||
|
[
|
||||||
|
Tutorials: [
|
||||||
|
~r'documentation/tutorials'
|
||||||
|
],
|
||||||
|
"How To": ~r'documentation/how_to',
|
||||||
|
Topics: ~r'documentation/topics',
|
||||||
|
DSLs: ~r'documentation/dsls'
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
defp docs do
|
defp docs do
|
||||||
[
|
[
|
||||||
main: "AshGraphql",
|
main: "getting-started-with-graphql",
|
||||||
source_ref: "v#{@version}",
|
source_ref: "v#{@version}",
|
||||||
logo: "logos/small-logo.png",
|
logo: "logos/small-logo.png",
|
||||||
extra_section: "GUIDES",
|
extra_section: "GUIDES",
|
||||||
|
@ -108,7 +114,17 @@ defmodule AshGraphql.MixProject do
|
||||||
],
|
],
|
||||||
Introspection: [
|
Introspection: [
|
||||||
AshGraphql.Resource.Info,
|
AshGraphql.Resource.Info,
|
||||||
AshGraphql.Api.Info
|
AshGraphql.Api.Info,
|
||||||
|
AshGraphql.Resource,
|
||||||
|
AshGraphql.Api,
|
||||||
|
AshGraphql.Resource.Action,
|
||||||
|
AshGraphql.Resource.ManagedRelationship,
|
||||||
|
AshGraphql.Resource.Mutation,
|
||||||
|
AshGraphql.Resource.Query
|
||||||
|
],
|
||||||
|
Errors: [
|
||||||
|
AshGraphql.Error,
|
||||||
|
AshGraphql.Errors
|
||||||
],
|
],
|
||||||
Miscellaneous: [
|
Miscellaneous: [
|
||||||
AshGraphql.Resource.Helpers
|
AshGraphql.Resource.Helpers
|
||||||
|
@ -140,18 +156,17 @@ defmodule AshGraphql.MixProject do
|
||||||
# Run "mix help deps" to learn about dependencies.
|
# Run "mix help deps" to learn about dependencies.
|
||||||
defp deps do
|
defp deps do
|
||||||
[
|
[
|
||||||
{:ash, ash_version("~> 2.11 and >= 2.11.8")},
|
{:ash, ash_version("~> 2.14 and >= 2.14.17")},
|
||||||
{:dataloader, "~> 1.0"},
|
|
||||||
{:absinthe_plug, "~> 1.4"},
|
{:absinthe_plug, "~> 1.4"},
|
||||||
{:absinthe, "~> 1.7"},
|
{:absinthe, "~> 1.7"},
|
||||||
{:jason, "~> 1.2"},
|
{:jason, "~> 1.2"},
|
||||||
{:ex_doc, "~> 0.22", only: [:dev, :test], runtime: false},
|
{:ex_doc, "~> 0.22", only: [:dev, :test], runtime: false},
|
||||||
{:ex_check, "~> 0.12.0", only: [:dev, :test]},
|
{:ex_check, "~> 0.12", only: [:dev, :test]},
|
||||||
{:credo, ">= 0.0.0", only: [:dev, :test], runtime: false},
|
{:credo, ">= 0.0.0", only: [:dev, :test], runtime: false},
|
||||||
{:dialyxir, ">= 0.0.0", only: [:dev, :test], runtime: false},
|
{:dialyxir, ">= 0.0.0", only: [:dev, :test], runtime: false},
|
||||||
{:sobelow, ">= 0.0.0", only: [:dev, :test], runtime: false},
|
{:sobelow, ">= 0.0.0", only: [:dev, :test], runtime: false},
|
||||||
{:git_ops, "~> 2.5.1", only: [:dev, :test]},
|
{:git_ops, "~> 2.5", only: [:dev, :test]},
|
||||||
{:excoveralls, "~> 0.13.0", only: [:dev, :test]},
|
{:excoveralls, "~> 0.13", only: [:dev, :test]},
|
||||||
{:mix_test_watch, "~> 1.0", only: :dev, runtime: false}
|
{:mix_test_watch, "~> 1.0", only: :dev, runtime: false}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
@ -169,8 +184,16 @@ defmodule AshGraphql.MixProject do
|
||||||
[
|
[
|
||||||
sobelow: "sobelow --skip",
|
sobelow: "sobelow --skip",
|
||||||
credo: "credo --strict",
|
credo: "credo --strict",
|
||||||
docs: ["docs", "ash.replace_doc_links"],
|
docs: [
|
||||||
"spark.formatter": "spark.formatter --extensions AshGraphql.Resource,AshGraphql.Api"
|
"spark.cheat_sheets",
|
||||||
|
"docs",
|
||||||
|
"ash.replace_doc_links",
|
||||||
|
"spark.cheat_sheets_in_search"
|
||||||
|
],
|
||||||
|
"spark.formatter": "spark.formatter --extensions AshGraphql.Resource,AshGraphql.Api",
|
||||||
|
"spark.cheat_sheets_in_search":
|
||||||
|
"spark.cheat_sheets_in_search --extensions AshGraphql.Resource,AshGraphql.Api",
|
||||||
|
"spark.cheat_sheets": "spark.cheat_sheets --extensions AshGraphql.Resource,AshGraphql.Api"
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
41
mix.lock
41
mix.lock
|
@ -1,47 +1,38 @@
|
||||||
%{
|
%{
|
||||||
"absinthe": {:hex, :absinthe, "1.7.1", "aca6f64994f0914628429ddbdfbf24212747b51780dae189dd98909da911757b", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.2.2 or ~> 1.3.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c0c4dbd93881fa3bfbad255608234b104b877c2a901850c1fe8c53b408a72a57"},
|
"absinthe": {:hex, :absinthe, "1.7.5", "a15054f05738e766f7cc7fd352887dfd5e61cec371fb4741cca37c3359ff74ac", [:mix], [{:dataloader, "~> 1.0.0 or ~> 2.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.2.2 or ~> 1.3.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:opentelemetry_process_propagator, "~> 0.2.1", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "22a9a38adca26294ad0ee91226168f5d215b401efd770b8a1b8fd9c9b21ec316"},
|
||||||
"absinthe_plug": {:hex, :absinthe_plug, "1.5.8", "38d230641ba9dca8f72f1fed2dfc8abd53b3907d1996363da32434ab6ee5d6ab", [:mix], [{:absinthe, "~> 1.5", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bbb04176647b735828861e7b2705465e53e2cf54ccf5a73ddd1ebd855f996e5a"},
|
"absinthe_plug": {:hex, :absinthe_plug, "1.5.8", "38d230641ba9dca8f72f1fed2dfc8abd53b3907d1996363da32434ab6ee5d6ab", [:mix], [{:absinthe, "~> 1.5", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bbb04176647b735828861e7b2705465e53e2cf54ccf5a73ddd1ebd855f996e5a"},
|
||||||
"ash": {:hex, :ash, "2.11.8", "f17d7032abdf7322c19ff32c231d695d6cc69d46c0803860c8cbe28cbbca7ee6", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1dffdd3d16f5914b8ba45f2a6cdb9e7e584c4213d9ec25f517a0b91734fff907"},
|
"ash": {:hex, :ash, "2.14.17", "d5b8f3a136d4ecd67645a0c539083c7ba0a35a388fce3f240f1cd16d06031b55", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "334efc54934100c1437f4c869148bd29a0fdb4dc5b912b470fdeaf15a718de33"},
|
||||||
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
|
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
|
||||||
"certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
|
|
||||||
"comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"},
|
"comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"},
|
||||||
"credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"},
|
"credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"},
|
||||||
"dataloader": {:hex, :dataloader, "1.0.10", "a42f07641b1a0572e0b21a2a5ae1be11da486a6790f3d0d14512d96ff3e3bbe9", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "54cd70cec09addf4b2ace14cc186a283a149fd4d3ec5475b155951bf33cd963f"},
|
|
||||||
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
|
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
|
||||||
"dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"},
|
"dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"},
|
||||||
"earmark_parser": {:hex, :earmark_parser, "1.4.31", "a93921cdc6b9b869f519213d5bc79d9e218ba768d7270d46fdcf1c01bacff9e2", [:mix], [], "hexpm", "317d367ee0335ef037a87e46c91a2269fef6306413f731e8ec11fc45a7efd059"},
|
"earmark_parser": {:hex, :earmark_parser, "1.4.35", "437773ca9384edf69830e26e9e7b2e0d22d2596c4a6b17094a3b29f01ea65bb8", [:mix], [], "hexpm", "8652ba3cb85608d0d7aa2d21b45c6fad4ddc9a1f9a1f1b30ca3a246f0acc33f6"},
|
||||||
"ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"},
|
"ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"},
|
||||||
"elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"},
|
"elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"},
|
||||||
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
|
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
|
||||||
"ets": {:hex, :ets, "0.8.1", "8ff9bcda5682b98493f8878fc9dbd990e48d566cba8cce59f7c2a78130da29ea", [:mix], [], "hexpm", "6be41b50adb5bc5c43626f25ea2d0af1f4a242fb3fad8d53f0c67c20b78915cc"},
|
"ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"},
|
||||||
"ex_check": {:hex, :ex_check, "0.12.0", "c0e2919ecc06afeaf62c52d64f3d91bd4bc7dd8deaac5f84becb6278888c967a", [:mix], [], "hexpm", "cfafa8ef97c2596d45a1f19b5794cb5c7f700f25d164d3c9f8d7ec17ee67cf42"},
|
"ex_check": {:hex, :ex_check, "0.15.0", "074b94c02de11c37bba1ca82ae5cc4926e6ccee862e57a485b6ba60fca2d8dc1", [:mix], [], "hexpm", "33848031a0c7e4209c3b4369ce154019788b5219956220c35ca5474299fb6a0e"},
|
||||||
"ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"},
|
"ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"},
|
||||||
"excoveralls": {:hex, :excoveralls, "0.13.4", "7b0baee01fe150ef81153e6ffc0fc68214737f54570dc257b3ca4da8e419b812", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "faae00b3eee35cdf0342c10b669a7c91f942728217d2a7c7f644b24d391e6190"},
|
"excoveralls": {:hex, :excoveralls, "0.17.1", "83fa7906ef23aa7fc8ad7ee469c357a63b1b3d55dd701ff5b9ce1f72442b2874", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "95bc6fda953e84c60f14da4a198880336205464e75383ec0f570180567985ae0"},
|
||||||
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
|
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
|
||||||
"git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"},
|
"git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"},
|
||||||
"git_ops": {:hex, :git_ops, "2.5.6", "dd01e0e4aedc69b532860ae72281902b00474f4f729b64193f5350daeec191e5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "00ed67de1f83684424196823d66e0f15c26117ee2ea6a3174b84de5b030b4112"},
|
"git_ops": {:hex, :git_ops, "2.6.0", "e0791ee1cf5db03f2c61b7ebd70e2e95cba2bb9b9793011f26609f22c0900087", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "b98fca849b18aaf490f4ac7d1dd8c6c469b0cc3e6632562d366cab095e666ffe"},
|
||||||
"hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~> 2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
|
|
||||||
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
|
|
||||||
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
|
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
|
||||||
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
|
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
|
||||||
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
|
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
|
||||||
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
|
"makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"},
|
||||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
|
|
||||||
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
|
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
|
||||||
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
|
"mix_test_watch": {:hex, :mix_test_watch, "1.1.1", "eee6fc570d77ad6851c7bc08de420a47fd1e449ef5ccfa6a77ef68b72e7e51ad", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "f82262b54dee533467021723892e15c3267349849f1f737526523ecba4e6baae"},
|
||||||
"mix_test_watch": {:hex, :mix_test_watch, "1.1.0", "330bb91c8ed271fe408c42d07e0773340a7938d8a0d281d57a14243eae9dc8c3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "52b6b1c476cbb70fd899ca5394506482f12e5f6b0d6acff9df95c7f1e0812ec3"},
|
|
||||||
"nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"},
|
"nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"},
|
||||||
"nimble_parsec": {:hex, :nimble_parsec, "1.3.0", "9e18a119d9efc3370a3ef2a937bf0b24c088d9c4bf0ba9d7c3751d49d347d035", [:mix], [], "hexpm", "7977f183127a7cbe9346981e2f480dc04c55ffddaef746bd58debd566070eef8"},
|
"nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"},
|
||||||
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
|
|
||||||
"picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"},
|
"picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"},
|
||||||
"plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"},
|
"plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"},
|
||||||
"plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
|
"plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
|
||||||
"sobelow": {:hex, :sobelow, "0.12.2", "45f4d500e09f95fdb5a7b94c2838d6b26625828751d9f1127174055a78542cf5", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "2f0b617dce551db651145662b84c8da4f158e7abe049a76daaaae2282df01c5d"},
|
"sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"},
|
||||||
"sourceror": {:hex, :sourceror, "0.12.3", "a2ad3a1a4554b486d8a113ae7adad5646f938cad99bf8bfcef26dc0c88e8fade", [:mix], [], "hexpm", "4d4e78010ca046524e8194ffc4683422f34a96f6b82901abbb45acc79ace0316"},
|
"sourceror": {:hex, :sourceror, "0.13.0", "c6ecc96ee3ae0e042e9082a9550a1989ea40182492dc29024a8d9d2b136e5014", [:mix], [], "hexpm", "d0a819491061cd26bfa4450d1c84301a410c19c1782a6577ce15853fc0e7e4e1"},
|
||||||
"spark": {:hex, :spark, "1.1.21", "8d09983e628d26edf358e7e8c0fa922667a049c4cfea6895d4b0820fc9bc1261", [:mix], [{:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "588dda298d6ea0a1d3f06c7978145ee799281bf8bcb0b418b7dae4b3d0be2e59"},
|
"spark": {:hex, :spark, "1.1.36", "a4cc1168fe94c24c90fbb3521921c4d3f9ec86642f84adc6da5ae62426189edd", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "ce03b3167f85fdc4416c9c5cee4aac4cc0ca8715d115631b68669be5e2e890f7"},
|
||||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
|
"stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"},
|
||||||
"stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"},
|
|
||||||
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
||||||
"typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"},
|
"typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"},
|
||||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
|
|
||||||
}
|
}
|
||||||
|
|
51
test/generic_actions_test.exs
Normal file
51
test/generic_actions_test.exs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
defmodule AshGraphql.CreateTest do
|
||||||
|
use ExUnit.Case, async: false
|
||||||
|
|
||||||
|
setup do
|
||||||
|
on_exit(fn ->
|
||||||
|
Application.delete_env(:ash_graphql, AshGraphql.Test.Api)
|
||||||
|
|
||||||
|
AshGraphql.TestHelpers.stop_ets()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "generic action queries can be run" do
|
||||||
|
resp =
|
||||||
|
"""
|
||||||
|
query {
|
||||||
|
postCount
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|> Absinthe.run(AshGraphql.Test.Schema)
|
||||||
|
|
||||||
|
assert {:ok, result} = resp
|
||||||
|
|
||||||
|
refute Map.has_key?(result, :errors)
|
||||||
|
|
||||||
|
assert %{data: %{"postCount" => 0}} = result
|
||||||
|
end
|
||||||
|
|
||||||
|
test "generic action mutations can be run" do
|
||||||
|
post = AshGraphql.Test.Api.create!(Ash.Changeset.new(AshGraphql.Test.Post, text: "foobar"))
|
||||||
|
|
||||||
|
resp =
|
||||||
|
"""
|
||||||
|
mutation {
|
||||||
|
randomPost {
|
||||||
|
id
|
||||||
|
comments{
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|> Absinthe.run(AshGraphql.Test.Schema)
|
||||||
|
|
||||||
|
assert {:ok, result} = resp
|
||||||
|
|
||||||
|
refute Map.has_key?(result, :errors)
|
||||||
|
post_id = post.id
|
||||||
|
|
||||||
|
assert %{data: %{"randomPost" => %{"id" => ^post_id, "comments" => []}}} = result
|
||||||
|
end
|
||||||
|
end
|
|
@ -114,6 +114,7 @@ defmodule AshGraphql.Test.Post do
|
||||||
list :keyset_paginated_posts, :keyset_paginated
|
list :keyset_paginated_posts, :keyset_paginated
|
||||||
list :paginated_posts_without_limit, :paginated_without_limit
|
list :paginated_posts_without_limit, :paginated_without_limit
|
||||||
list :paginated_posts_limit_not_required, :paginated_limit_not_required
|
list :paginated_posts_limit_not_required, :paginated_limit_not_required
|
||||||
|
action(:post_count, :count)
|
||||||
end
|
end
|
||||||
|
|
||||||
managed_relationships do
|
managed_relationships do
|
||||||
|
@ -149,6 +150,9 @@ defmodule AshGraphql.Test.Post do
|
||||||
destroy :delete_post, :destroy
|
destroy :delete_post, :destroy
|
||||||
destroy :delete_best_post, :destroy, read_action: :best_post, identity: false
|
destroy :delete_best_post, :destroy, read_action: :best_post, identity: false
|
||||||
destroy :delete_post_with_error, :destroy_with_error
|
destroy :delete_post_with_error, :destroy_with_error
|
||||||
|
|
||||||
|
# this is a mutation just for testing
|
||||||
|
action(:random_post, :random)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -176,6 +180,33 @@ defmodule AshGraphql.Test.Post do
|
||||||
change(AshGraphql.Test.ForceChangeId)
|
change(AshGraphql.Test.ForceChangeId)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
action :count, :integer do
|
||||||
|
argument(:published, :boolean)
|
||||||
|
|
||||||
|
run(fn input, _ ->
|
||||||
|
query =
|
||||||
|
if input.arguments[:published] do
|
||||||
|
Ash.Query.filter(__MODULE__, published == true)
|
||||||
|
else
|
||||||
|
__MODULE__
|
||||||
|
end
|
||||||
|
|
||||||
|
input.api.count(query)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
action :random, :struct do
|
||||||
|
constraints(instance_of: __MODULE__)
|
||||||
|
argument(:published, :boolean)
|
||||||
|
allow_nil? true
|
||||||
|
|
||||||
|
run(fn input, _ ->
|
||||||
|
__MODULE__
|
||||||
|
|> Ash.Query.limit(1)
|
||||||
|
|> input.api.read_one()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
create :create_confirm do
|
create :create_confirm do
|
||||||
argument(:confirmation, :string)
|
argument(:confirmation, :string)
|
||||||
validate(confirm(:text, :confirmation))
|
validate(confirm(:text, :confirmation))
|
||||||
|
|
|
@ -27,8 +27,4 @@ defmodule AshGraphql.Test.Schema do
|
||||||
value(:open, description: "The post is open")
|
value(:open, description: "The post is open")
|
||||||
value(:closed, description: "The post is closed")
|
value(:closed, description: "The post is closed")
|
||||||
end
|
end
|
||||||
|
|
||||||
def plugins do
|
|
||||||
[Absinthe.Middleware.Dataloader | Absinthe.Plugin.defaults()]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue