-
Notifications
You must be signed in to change notification settings - Fork 180
Expand file tree
/
Copy pathsse.ex
More file actions
156 lines (127 loc) · 4.12 KB
/
Copy pathsse.ex
File metadata and controls
156 lines (127 loc) · 4.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
defmodule Absinthe.Plug.Incremental.SSE do
@moduledoc """
Server-Sent Events (SSE) transport for incremental delivery.
This module implements incremental delivery over HTTP using SSE,
allowing @defer and @stream directives to work over standard HTTP connections.
## Usage
# In your Phoenix router
pipeline :graphql_streaming do
plug Absinthe.Plug.Incremental.SSE
end
scope "/api" do
pipe_through :graphql_streaming
post "/graphql/stream", GraphQLController, :stream_query
end
## Example Query with Streaming
query GetUsers {
users @stream(initialCount: 2, label: "users") {
id
name
... @defer(label: "profile") {
profile {
bio
avatar
}
}
}
}
The response will be delivered as a series of SSE events:
- `initial` event with the first 2 users
- `incremental` events for remaining users and deferred profiles
- `complete` event when all data is delivered
"""
use Absinthe.Incremental.Transport
import Plug.Conn
require Logger
alias Absinthe.Plug.Incremental.SSE.{EventFormatter, ConnectionManager, QueryProcessor}
@impl true
def init(conn, options) do
if ConnectionManager.accepts_sse?(conn) do
conn = ConnectionManager.setup_sse_headers(conn)
if Keyword.get(options, :keep_alive, true) do
ConnectionManager.schedule_keep_alive()
end
{:ok, %{
conn: conn,
operation_id: Keyword.get(options, :operation_id),
event_id: 0,
options: options
}}
else
{:error, :sse_not_accepted}
end
end
@impl true
def send_initial(state, response) do
event_data = EventFormatter.format_event("initial", response, state.event_id)
case chunk(state.conn, event_data) do
{:ok, conn} ->
{:ok, %{state |
conn: conn,
event_id: state.event_id + 1
}}
{:error, reason} ->
Logger.error("Failed to send initial SSE response: #{inspect(reason)}")
{:error, {:transport_error, reason}}
end
end
@impl true
def send_incremental(state, response) do
event_data = EventFormatter.format_event("incremental", response, state.event_id)
case chunk(state.conn, event_data) do
{:ok, conn} ->
{:ok, %{state |
conn: conn,
event_id: state.event_id + 1
}}
{:error, reason} ->
Logger.error("Failed to send incremental SSE response: #{inspect(reason)}")
{:error, {:transport_error, reason}}
end
end
@impl true
def complete(state) do
event_data = EventFormatter.format_event("complete", %{}, state.event_id)
case chunk(state.conn, event_data) do
{:ok, conn} ->
chunk(conn, "")
:ok
{:error, reason} ->
Logger.error("Failed to send complete SSE event: #{inspect(reason)}")
{:error, {:transport_error, reason}}
end
end
@impl true
def handle_error(state, error) do
error_response = EventFormatter.format_error_response(error)
event_data = EventFormatter.format_event("error", error_response, state.event_id)
case chunk(state.conn, event_data) do
{:ok, conn} ->
{:ok, %{state |
conn: conn,
event_id: state.event_id + 1
}}
{:error, reason} ->
Logger.error("Failed to send error SSE event: #{inspect(reason)}")
{:error, {:transport_error, reason}}
end
end
@doc """
Handle keep-alive to prevent connection timeout.
"""
def handle_keep_alive(state) do
case chunk(state.conn, ": keep-alive\n\n") do
{:ok, conn} ->
ConnectionManager.schedule_keep_alive()
{:ok, %{state | conn: conn}}
{:error, _reason} ->
{:error, :connection_closed}
end
end
@doc """
Process a GraphQL query with incremental delivery over SSE.
"""
def process_query(conn, schema, query, variables \\ %{}, options \\ []) do
QueryProcessor.process(conn, schema, query, variables, options)
end
end