Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: subscription dsl #97

Merged
merged 48 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
a5c40fe
copy absinthe subscription test
barnabasJ Oct 14, 2023
e6f418f
wip
barnabasJ Oct 15, 2023
f7227b4
wip
barnabasJ Oct 27, 2023
b978148
wip
barnabasJ Nov 24, 2023
05fc1f9
a step in the right direction?
barnabasJ Dec 8, 2023
026f2f1
wip
barnabasJ Jan 4, 2024
90ad223
add subscription field to schema
barnabasJ Jan 8, 2024
5b64553
returned my first result from a subscription
barnabasJ Jan 10, 2024
f610c74
wip
barnabasJ Feb 6, 2024
3eaa6bf
subscribed and recieved data in playground
barnabasJ Feb 20, 2024
fbe1700
create config that can use the filter to create a context_id for de-d…
barnabasJ Feb 21, 2024
c26af7a
move resolve logic in existing resolver module
barnabasJ Mar 8, 2024
a96587b
wip
barnabasJ May 6, 2024
cb576c8
rebase on main
barnabasJ Jul 1, 2024
62b258f
add endpoint with custom run_docset function
barnabasJ Aug 5, 2024
2de3846
add missing alias
barnabasJ Aug 5, 2024
e1d2d23
handle not found case
barnabasJ Aug 5, 2024
a8fe431
use the configured read action to get the data in the resolver
barnabasJ Aug 5, 2024
6eaf388
only listen to the configured actions
barnabasJ Aug 5, 2024
4afd8ea
do some renaming
barnabasJ Aug 7, 2024
4aa4fc5
add actor function
barnabasJ Aug 7, 2024
2d6501b
handle filter
barnabasJ Aug 7, 2024
6b2addd
wip: add subscription result type
barnabasJ Aug 7, 2024
16a2fff
use trace and handle arguments
barnabasJ Aug 15, 2024
772a054
clean up part 1
barnabasJ Aug 15, 2024
617b625
handle destroys
barnabasJ Aug 15, 2024
8a565cf
update test
barnabasJ Aug 26, 2024
c403c09
test all mutation types with subscriptions
barnabasJ Sep 23, 2024
e14d591
format code
barnabasJ Sep 23, 2024
d324eef
move all the subscription files into the same folder
barnabasJ Sep 23, 2024
76d4c86
add some policy tests for subscriptions
barnabasJ Sep 24, 2024
16e8a43
filer out errors without a code
barnabasJ Sep 24, 2024
7dd9df0
test data and only load necessary stuff
barnabasJ Sep 25, 2024
3176987
remove dbg
barnabasJ Sep 25, 2024
9dd9c58
make subscriptions opt_in
barnabasJ Sep 25, 2024
4303af8
support action_types and arguments in read actions
barnabasJ Sep 26, 2024
995dd7b
add support for adding subscriptions on the domain
barnabasJ Sep 26, 2024
9067daa
only add the fields than will get data to the subcription result type
barnabasJ Sep 26, 2024
6da3690
remove unecessary number from variable
barnabasJ Sep 26, 2024
2dc91be
test field policies in subscriptions
barnabasJ Sep 26, 2024
0a287ec
remove unecessary param
barnabasJ Sep 26, 2024
f2d2d61
check if all actions configured in subscriptions actually exist
barnabasJ Sep 26, 2024
381cddf
generate cheat sheets
barnabasJ Sep 26, 2024
fde8217
update formatter
barnabasJ Sep 26, 2024
8a6337d
fix: credo
barnabasJ Sep 26, 2024
3b3b895
add docs
barnabasJ Sep 27, 2024
450ee56
fix docs
barnabasJ Sep 27, 2024
36c9f6f
Update documentation/topics/use-subscriptions-with-graphql.md
barnabasJ Sep 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .formatter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ spark_locals_without_parens = [
action: 2,
action: 3,
action: 4,
action_types: 1,
actions: 1,
actor: 1,
allow_nil?: 1,
argument_input_types: 1,
argument_names: 1,
Expand Down Expand Up @@ -47,6 +50,7 @@ spark_locals_without_parens = [
paginate_relationship_with: 1,
paginate_with: 1,
primary_key_delimiter: 1,
pubsub: 1,
read_action: 1,
read_one: 2,
read_one: 3,
Expand All @@ -58,6 +62,9 @@ spark_locals_without_parens = [
show_fields: 1,
show_metadata: 1,
show_raised_errors?: 1,
subscribe: 1,
subscribe: 2,
subscribe: 3,
tracer: 1,
type: 1,
type_name: 1,
Expand Down
5 changes: 5 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ config :ash, :validate_domain_config_inclusion?, false

config :logger, level: :warning

config :ash, :pub_sub, debug?: true
config :logger, level: :info

config :ash_graphql, :subscriptions, true

if Mix.env() == :dev do
config :git_ops,
mix_project: AshGraphql.MixProject,
Expand Down
74 changes: 71 additions & 3 deletions documentation/dsls/DSL:-AshGraphql.Domain.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Domain level configuration for GraphQL
* update
* destroy
* action
* [subscriptions](#graphql-subscriptions)
* subscribe


### Examples
Expand Down Expand Up @@ -269,9 +271,9 @@ Mutations (create/update/destroy actions) to expose for the resource.
### Examples
```
mutations do
create :create_post, :create
update :update_post, :update
destroy :destroy_post, :destroy
create Post, :create_post, :create
update Post, :update_post, :update
destroy Post, :destroy_post, :destroy
end

```
Expand Down Expand Up @@ -445,6 +447,72 @@ action :check_status, :check_status
Target: `AshGraphql.Resource.Action`


## graphql.subscriptions
Subscriptions to expose for the resource.


### Nested DSLs
* [subscribe](#graphql-subscriptions-subscribe)


### Examples
```
subscription do
subscribe Post, :post_created do
action_types(:create)
end
end

```




## graphql.subscriptions.subscribe
```elixir
subscribe resource, name
```


A subscription to listen for changes on the resource



### Examples
```
subscribe :post_created do
action_types(:create)
end

```



### Arguments

| Name | Type | Default | Docs |
|------|------|---------|------|
| [`resource`](#graphql-subscriptions-subscribe-resource){: #graphql-subscriptions-subscribe-resource } | `module` | | The resource that the action is defined on |
| [`name`](#graphql-subscriptions-subscribe-name){: #graphql-subscriptions-subscribe-name } | `atom` | | The name to use for the subscription. |
### Options

| Name | Type | Default | Docs |
|------|------|---------|------|
| [`actor`](#graphql-subscriptions-subscribe-actor){: #graphql-subscriptions-subscribe-actor } | `(any -> any) \| module` | | The actor to use for authorization. |
| [`actions`](#graphql-subscriptions-subscribe-actions){: #graphql-subscriptions-subscribe-actions } | `list(atom) \| atom` | | The create/update/destroy actions the subsciption should listen to. |
| [`action_types`](#graphql-subscriptions-subscribe-action_types){: #graphql-subscriptions-subscribe-action_types } | `list(atom) \| atom` | | The type of actions the subsciption should listen to. |
| [`read_action`](#graphql-subscriptions-subscribe-read_action){: #graphql-subscriptions-subscribe-read_action } | `atom` | | The read action to use for reading data |
| [`hide_inputs`](#graphql-subscriptions-subscribe-hide_inputs){: #graphql-subscriptions-subscribe-hide_inputs } | `list(atom)` | `[]` | A list of inputs to hide from the subscription, usable if the read action has arguments. |





### Introspection

Target: `AshGraphql.Resource.Subscription`





Expand Down
76 changes: 76 additions & 0 deletions documentation/dsls/DSL:-AshGraphql.Resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Configuration for a given resource in graphql
* update
* destroy
* action
* [subscriptions](#graphql-subscriptions)
* subscribe
* [managed_relationships](#graphql-managed_relationships)
* managed_relationship

Expand Down Expand Up @@ -464,6 +466,80 @@ action :check_status, :check_status
Target: `AshGraphql.Resource.Action`


## graphql.subscriptions
Subscriptions (notifications) to expose for the resource.


### Nested DSLs
* [subscribe](#graphql-subscriptions-subscribe)


### Examples
```
subscriptions do
subscribe :bucket_created do
actions :create
read_action :read
end
end

```




### Options

| Name | Type | Default | Docs |
|------|------|---------|------|
| [`pubsub`](#graphql-subscriptions-pubsub){: #graphql-subscriptions-pubsub .spark-required} | `module` | | The pubsub module to use for the subscription |



## graphql.subscriptions.subscribe
```elixir
subscribe name
```


A subscription to listen for changes on the resource



### Examples
```
subscribe :post_created do
action_types(:create)
end

```



### Arguments

| Name | Type | Default | Docs |
|------|------|---------|------|
| [`name`](#graphql-subscriptions-subscribe-name){: #graphql-subscriptions-subscribe-name } | `atom` | | The name to use for the subscription. |
### Options

| Name | Type | Default | Docs |
|------|------|---------|------|
| [`actor`](#graphql-subscriptions-subscribe-actor){: #graphql-subscriptions-subscribe-actor } | `(any -> any) \| module` | | The actor to use for authorization. |
| [`actions`](#graphql-subscriptions-subscribe-actions){: #graphql-subscriptions-subscribe-actions } | `list(atom) \| atom` | | The create/update/destroy actions the subsciption should listen to. |
| [`action_types`](#graphql-subscriptions-subscribe-action_types){: #graphql-subscriptions-subscribe-action_types } | `list(atom) \| atom` | | The type of actions the subsciption should listen to. |
| [`read_action`](#graphql-subscriptions-subscribe-read_action){: #graphql-subscriptions-subscribe-read_action } | `atom` | | The read action to use for reading data |
| [`hide_inputs`](#graphql-subscriptions-subscribe-hide_inputs){: #graphql-subscriptions-subscribe-hide_inputs } | `list(atom)` | `[]` | A list of inputs to hide from the subscription, usable if the read action has arguments. |





### Introspection

Target: `AshGraphql.Resource.Subscription`


## graphql.managed_relationships
Generates input objects for `manage_relationship` arguments on resource actions.

Expand Down
76 changes: 75 additions & 1 deletion documentation/topics/use-subscriptions-with-graphql.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Using Subscriptions

The AshGraphql DSL does not currently support subscriptions. However, you can do this with Absinthe direclty, and use `AshGraphql.Subscription.query_for_subscription/3`. Here is an example of how you could do this for a subscription for a single record. This example could be extended to support lists of records as well.
You can do this with Absinthe direclty, and use `AshGraphql.Subscription.query_for_subscription/3`. Here is an example of how you could do this for a subscription for a single record. This example could be extended to support lists of records as well.
barnabasJ marked this conversation as resolved.
Show resolved Hide resolved

```elixir
# in your absinthe schema file
Expand All @@ -27,3 +27,77 @@ subscription do
end
end
```

## Subscription DSL (beta)

The subscription DSL is currently in beta and before using it you have to enable them in your config.

```elixir
config :ash_graphql, :policies, show_policy_breakdowns?: true
```

First you'll need to do some setup, follow the the [setup guide](https://hexdocs.pm/absinthe/subscriptions.html#absinthe-phoenix-setup)
in the absinthe docs, but instead of using `Absinthe.Pheonix.Endpoint` use `AshGraphql.Subscription.Endpoint`.

Afterwards add an empty subscription block to your schema module.

```elixir
defmodule MyAppWeb.Schema do
...

subscription do
end
end
```

Now you can define subscriptions on your resource or domain

```elixir
defmodule MyApp.Resource do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets,
extensions: [AshGraphql.Resource]

graphql do
subscriptions do
subscribe :resource_created do
action_types :create
end
end
end
end
```

For further Details checkout the DSL docs for [resource](/documentation/dsls/DSL:-AshGraphql.Resource.md#graphql-subscriptions) and [domain](/documentation/dsls/DSL:-AshGraphql.Domain.md#graphql-subscriptions)

### Deduplication

By default, Absinthe will deduplicate subscriptions based on the `context_id`.
We use the some of the context like actor and tenant to create a `context_id` for you.

If you want to customize the deduplication you can do so by adding a actor function to your subscription.
This function will be called with the actor that subscribes and you can return a more generic actor, this
way you can have one actor for multiple users, which will lead to less resolver executions.

```elixir
defmodule MyApp.Resource do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets,
extensions: [AshGraphql.Resource]

graphql do
subscriptions do
subscribe :resource_created do
action_types :create
actor fn actor ->
if check_actor(actor) do
%{id: "your generic actor", ...}
else
actor
end
end
end
end
end
end
```
19 changes: 16 additions & 3 deletions lib/ash_graphql.ex
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ defmodule AshGraphql do
@dialyzer {:nowarn_function, {:run, 2}}
def run(blueprint, _opts) do
domain = unquote(domain)

action_middleware = unquote(action_middleware)

all_domains = unquote(Enum.map(domains, &elem(&1, 0)))
Expand Down Expand Up @@ -204,6 +205,18 @@ defmodule AshGraphql do
Absinthe.Blueprint.add_field(blueprint, "RootMutationType", mutation)
end)

blueprint_with_subscriptions =
domain
|> AshGraphql.Domain.subscriptions(
all_domains,
unquote(resources),
action_middleware,
unquote(schema)
)
|> Enum.reduce(blueprint_with_mutations, fn subscription, blueprint ->
Absinthe.Blueprint.add_field(blueprint, "RootSubscriptionType", subscription)
end)

managed_relationship_types =
AshGraphql.Resource.managed_relationship_definitions(
Process.get(:managed_relationship_requirements, []),
Expand All @@ -212,7 +225,7 @@ defmodule AshGraphql do
|> Enum.uniq_by(& &1.identifier)
|> Enum.reject(fn type ->
existing_types =
case blueprint_with_mutations do
case blueprint_with_subscriptions do
%{schema_definitions: [%{type_definitions: type_definitions}]} ->
type_definitions

Expand Down Expand Up @@ -293,7 +306,7 @@ defmodule AshGraphql do
end

new_defs =
List.update_at(blueprint_with_mutations.schema_definitions, 0, fn schema_def ->
List.update_at(blueprint_with_subscriptions.schema_definitions, 0, fn schema_def ->
%{
schema_def
| type_definitions:
Expand All @@ -302,7 +315,7 @@ defmodule AshGraphql do
}
end)

{:ok, %{blueprint_with_mutations | schema_definitions: new_defs}}
{:ok, %{blueprint_with_subscriptions | schema_definitions: new_defs}}
end
end

Expand Down
Loading
Loading