Filo.Client (Filo v0.2.0)

Copy Markdown View Source

A Hrana client — the mirror of Filo.Plug.

Where the server decodes client requests and encodes results, a client encodes requests and decodes results, so it reuses the same protocol codec the server is built on: Filo.Value for cell values and Filo.StmtResult for the shape of a result. There is no second implementation of the wire format to drift from the server's.

Model

One Filo.Client is one Hrana stream over one owned transport connection. The first request opens the stream; each response carries a baton that threads into the next request, so a transaction is a burst of execute/3s on the held connection — the same per-statement round-trip model libSQL SDKs use over POST /v2/pipeline. The client is an immutable struct you thread through your own process; hold one per concurrent stream (on the BEAM, that is one cheap process each).

{:ok, c} = Filo.Client.connect("http://localhost:8080", authority: "acme.example")
{:ok, _res, c} = Filo.Client.execute(c, "CREATE TABLE t (a INTEGER, b TEXT)")
{:ok, _res, c} = Filo.Client.execute(c, "INSERT INTO t VALUES (?, ?)", [1, "x"])
{:ok, res, c}  = Filo.Client.execute(c, "SELECT a, b FROM t WHERE a = ?", [1])
res.rows       #=> [[1, "x"]]
:ok = Filo.Client.close(c)

Transport

The HTTP round-trip is a Filo.Client.Transport behaviour so Filo keeps its no-concrete-transport posture: the default Filo.Client.Transport.Mint needs the optional :mint dependency, and a host that would rather drive Finch/Req/:gun passes its own :transport.

Scope

This is the Hrana 2/3 HTTP JSON pipeline (execute, close, baton threading) — enough to drive a shard end to end. Protobuf, the WebSocket binding, cursors, and batches are natural follow-ups that reuse the same codec.

Summary

Functions

Closes the stream (a best-effort close request that lets the server release the connection) and the underlying transport.

Opens a Hrana stream to url (e.g. "http://host:8080").

Runs one SQL statement on the stream, binding args positionally.

Sends a raw list of Hrana stream-request maps as one pipeline and returns the raw results list (and the advanced client). The low-level primitive execute/3 is built on; use it to send request types this module has no sugar for yet (batch, describe, get_autocommit).

Drops the transport connection and opens a fresh one, resetting the baton.

Types

t()

@type t() :: %Filo.Client{
  authority: String.t() | nil,
  baton: String.t() | nil,
  headers: [{String.t(), String.t()}],
  path: String.t(),
  timeout: timeout(),
  transport: module(),
  transport_opts: keyword(),
  transport_state: term(),
  uri: URI.t()
}

Functions

close(client)

@spec close(t()) :: :ok

Closes the stream (a best-effort close request that lets the server release the connection) and the underlying transport.

connect(url, opts \\ [])

@spec connect(
  String.t(),
  keyword()
) :: {:ok, t()} | {:error, term()}

Opens a Hrana stream to url (e.g. "http://host:8080").

Options

  • :transport — a Filo.Client.Transport module. Default Filo.Client.Transport.Mint (requires the optional :mint dep).
  • :authority — the Host header value. Set this to route by subdomain through a load balancer ("acme.fathom.example"), exactly as a libSQL SDK does. Default: the URL's host.
  • :auth_token — a bearer token sent as Authorization: Bearer <token> (libSQL's authToken over HTTP). Default: none.
  • :version"v2" or "v3" (the pipeline path). Default "v2".
  • :headers — extra request headers. Default [].
  • :timeout — per-request timeout in ms. Default 15_000.
  • :transport_opts — extra options passed to the transport's connect/2.

execute(client, sql, args \\ [])

@spec execute(t(), String.t(), [Filo.Value.native()]) ::
  {:ok, Filo.StmtResult.t(), t()}
  | {:error, Filo.Error.t() | {:transport, term()}, t()}

Runs one SQL statement on the stream, binding args positionally.

Returns the decoded Filo.StmtResult and the advanced client (its baton threaded for the next call). A SQL-level failure is {:error, %Filo.Error{}, client} with the stream still open (its baton advanced), matching the server. A transport failure is {:error, {:transport, reason}, client}; recover with reconnect/1.

Values are encoded via Filo.Value, so integers, floats, text, nil, and {:blob, binary} all bind correctly.

pipeline(client, requests)

@spec pipeline(t(), [map()]) ::
  {:ok, [map()], t()} | {:error, Filo.Error.t() | {:transport, term()}, t()}

Sends a raw list of Hrana stream-request maps as one pipeline and returns the raw results list (and the advanced client). The low-level primitive execute/3 is built on; use it to send request types this module has no sugar for yet (batch, describe, get_autocommit).

reconnect(client)

@spec reconnect(t()) :: {:ok, t()} | {:error, term()}

Drops the transport connection and opens a fresh one, resetting the baton.

The server rolls back any uncommitted transaction on the abandoned stream. Use it to recover from a transport error (a stale keepalive the server closed, or a 502 while a load balancer moves the shard) without losing the client.