Bsdkrun.GraphQLSocket (bsdkrun_ex v0.2.1)

Copy Markdown View Source

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-ws message protocol on top of it: connection_init -> wait for connection_ack (queuing any subscribe sent in the meantime, flushed once the ack lands) -> subscribe / next / error / complete, plus ping/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

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

subscribe(pid, query, variables, handler)

@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} — the payload.data of a next message.
  • {:error, %Bsdkrun.Error{}} — a GraphQL error message, or the socket closing while this subscription was still open.
  • :complete — a complete message.

Returns {:ok, subscription_id}, immediately — the subscribe message may be queued internally until connection_ack arrives.

unsubscribe(pid, id)

@spec unsubscribe(pid(), String.t()) :: :ok

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.