One shared graphql-transport-ws socket per Bsdkrun.Client "connection"
(same url + token), multiplexing every live subscription over it —
exec/4, shell/3, follow_logs/3 and the subscribe/4 escape hatch all
share one socket per client. Started lazily on the first subscription,
under Bsdkrun.Client.SocketSupervisor (a DynamicSupervisor), and
registered in Bsdkrun.Client.Registry by {url, token} so every caller
using the same Bsdkrun.Client finds the same socket. See the private
ensure_conn/1 function in Bsdkrun.Client.
Two protocol layers are hand-rolled here, per the daemon's contract
(daemon/README.md, web/src/lib/graphql.ts) — no hex dependency for
either:
- RFC 6455 WebSocket framing — the HTTP Upgrade handshake and
frame encode/decode are pure functions in
Bsdkrun.WsFrame. - The
graphql-transport-wsmessage protocol on top of it:connection_init-> wait forconnection_ack(queuing anysubscribesent in the meantime, flushed once the ack lands) ->subscribe/next/error/complete, plusping/pong.
A subscription is registered with a handler function, (id, event) -> any, invoked from this GenServer's own process — so a slow handler
(e.g. a blocking callback) delays this socket's frame processing for every
other subscription sharing it. Bsdkrun.Client keeps handlers cheap
(message-send or a user callback) precisely to avoid that.
Summary
Functions
Returns a specification to start this module under a supervisor.
Start a subscription for query/variables. handler is called as
handler.(subscription_id, event) for every event delivered to it
Cancel a subscription. Idempotent: an unknown id, or a socket that already died, is not an error. Closes the socket (and this process exits) once no subscription is left on it.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec subscribe(pid(), String.t(), map(), (String.t(), term() -> any())) :: {:ok, String.t()} | {:error, Bsdkrun.Error.t()}
Start a subscription for query/variables. handler is called as
handler.(subscription_id, event) for every event delivered to it:
{:next, data}— thepayload.dataof anextmessage.{:error, %Bsdkrun.Error{}}— a GraphQLerrormessage, or the socket closing while this subscription was still open.:complete— acompletemessage.
Returns {:ok, subscription_id}, immediately — the subscribe message may
be queued internally until connection_ack arrives.
Cancel a subscription. Idempotent: an unknown id, or a socket that already died, is not an error. Closes the socket (and this process exits) once no subscription is left on it.