How to add a phase to the Absinthe subscription pipeline?

@benwilson512

The pipeline docs say to See Absinthe.Phoenix on adjusting the document pipeline for GraphQL over Phoenix channels, but I don’t see anything about adjusting the pipeline in those docs. How can I add a phase to the Absinthe.Phoenix pipeline?

Answering my own question here. You can configure a custom pipeline like this:

use Absinthe.Phoenix.Socket, schema: App.Web.GraphQL.Schema, pipeline: App.Web.GraphQL.SomeCustomPipeline

It would be great if this could be added to the docs.

Hey @michaelcaterisano yeah that does seem to be missing from the docs. Basically, just like plug you do:

pipeline: {SomeModule, :some_function}

Then you have some_function that tweaks the pipeline like it would for a plug.

EDIT: OOPS didn’t see your reply. Yup you have it, PR welcome for the docs!

1 Like

Thanks @benwilson512. It seems that :some_function is passed two arguments; the first is a reference to the GraphQL schema module, and the second is a keyword list with a :variables and :context key. It’s not clear to me what this function should return though. I tried returning the schema, that didn’t work. Any advice?

EDIT: my memory is wrong

@michaelcaterisano Apologies, what you want is:

def some_function(schema, config) do
  schema
  |> Absinthe.Phoenix.Channel.default_pipeline(config)
  |> # do your stuff here.
end
1 Like

EDIT: I see your reply now, I’ll try that, thanks!

@benwilson512 I’m trying to insert a phase in this pipeline. I tried:

 schema
    |> Absinthe.Phoenix.Channel.default_pipeline(config)
    |> Absinthe.Pipeline.insert_after(Absinthe.Phase.Document.Result, API.Web.GraphQL.Phase.MyPhase)

My phase looks like:

defmodule API.Web.GraphQL.Phase.MyPhase do
  @behaviour Absinthe.Phase

  @impl Absinthe.Phase
  def run(blueprint, _) do
    blueprint
  end
end

but it doesn’t seem to be reaching my phase. Do I need to be implementing a different behavior here?

1 Like

@michaelcaterisano is this for subscriptions or for normal queries?

This is for subscriptions

Ah, this is a bit trickier because subscriptions are processed in two distinct situations. You’ve got the first pass which registers the subscription itself, and then you have a later set of phases that are run every time data is published to the subscription. When are you trying to run your phase?

I’m trying to run a phase when data is published to the subscription

It may not be possible to run additional phases after the result phase on subscription publish right now, I’d have to dig into the code. Can you elaborate on your use case a bit?

EDIT: You could probably hack it by replacing the result phase with your own by doing:

|> default_pipeline(Keyword.put(opts, :result_phase, MyApp.ResultPhase)

and then in your result phase first running the regular result phase then doing whatever you want.

It’s the same use case as my question here: Conditionally send subscription events - #2 by benwilson512

I want to prevent certain events from being sent back to the subscriber

Oh sorry I didn’t put 2 and 2 together!

The root issue you aren’t gonna get around is this: absinthe/lib/absinthe/subscription/local.ex at main · absinthe-graphql/absinthe · GitHub

At the end of the day the GraphQL doc needs to return a result or the background publisher will crash, and then it’s going to unconditionally publish that result. These results are fastlaned for performance, so you can’t even easily filter them out in the channel.

This is from your other thread but yeah, Absinthe wraps the publication call in a try so that the mutation that is triggering the publish doesn’t crash just cause a subscription fails. You can see that rescue actually in the code I linked above.

A quick and dirty answer here would be to fork Absinthe and change that code to look at some value and have it not publish.

I think your overall goal here is a good one and one that Absinthe should support. I can’t promise a timeline on when it will though.

@benwilson512 Thanks, that makes sense. I was able to get something working after forking the library.

That spot in the code you linked gets the resolved value of the subscription document, ie only the data queried by the client. I’m wondering if there’s somewhere I could access the value returned by the subscription field resolver directly. I’m thinking I would add some metadata field to the map returned by the subscription resolver, like ignore: true, and match on it. That way the client doesn’t have to know about it. Otherwise the field has to be present in the subscription request in order for it to show up as data in local.ex.

@benwilson512 I ran across this github issue from a while ago: Can't track socket disconnect in __absinthe__:control channel · Issue #39 · absinthe-graphql/absinthe_phoenix · GitHub

It looks like folks were having success intercepting subscription messages this way. I haven’t gotten it to work yet, but does this seems like it would work to you? I’m interested in outgoing messages, not Presence, but maybe there’s something here?