# `DrinkMeAlice.Session`

A caller-owned, synchronous SurrealDB session: one socket, owned by the
process that opened it, one request at a time, no background process.

For short-lived work — scripts, provisioning, migrations, drills —
where a supervised `DrinkMeAlice.Conn` owner is more machinery than
the job needs. Same transport (`Ws`), same frame guard and same bounded
JSON decode profile as `Conn`; no credits (the caller is the only
sender) and no reconnect (a lost transport is reported, and the caller
opens a fresh session).

The session value carries the Mint/WebSocket parser state; every call
returns the updated session, so fragmented replies never leave stale
buffer state for the next call. Close it with `close/1` — `Ws.close/1`
is idempotent, and a dead session is safe to close.

Credentials never enter error values: failures are sanitized codes.

## Config

`host`, `port`, `ns`, `db`, `user`, `pass`, `signin` (`:root` |
`:namespace` | `:database`, default `:root`), `connect_timeout`
(default 2_000), `reply_budget_ms` (default 5_000, at most 60_000).
A root session may set `ns: nil` — or omit `ns` entirely; the two are
normalized to the same thing — and a root or namespace session may set
`db: nil` or omit it.

# `t`

```elixir
@opaque t()
```

# `close`

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

Close the socket. Idempotent.

# `open`

```elixir
@spec open(map() | keyword()) :: {:ok, t()} | {:error, term()}
```

Connect, sign in, `use ns db`. Closes the socket on any failed step —
including an unexpected exception — and never raises or exits: an
omitted optional key and an explicit `nil` are the same
configuration, and even a malformed top-level configuration form is
a typed `{:error, _}`, never an exception.

# `query`

```elixir
@spec query(t(), binary(), map()) :: {:ok, t(), list()} | {:error, term()}
```

Run one `query`. `{:ok, session, results}` where `results` is the
per-statement array (callers check every status), or
`{:error, reason}` — `{:rpc_error, error_map}` for a server refusal
(the whole object, so `Outcome.session_refusal?/1` can read it),
`{:lost, reason}` when the request may have been sent but no reply
arrived. After `{:lost, _}` the session is not reusable: close it.

# `rpc`

```elixir
@spec rpc(t(), binary(), list()) :: {:ok, t(), term()} | {:error, term()}
```

Run one RPC method; `{:ok, session, result}` or `{:error, reason}`.

# `with_session`

```elixir
@spec with_session(map() | keyword(), (function() -&gt; term())) ::
  :ok | {:ok, term()} | {:error, term()}
```

Open a session, run `fun.(query)` and close — the socket is closed on
every exit path. `query` is `(sql, vars) -> {:ok, results} | {:error,
reason}`. Returns `{:ok, fun_result}` or `{:error, reason}` when the
session could not open; a body that returns `{:error, r}` surfaces as
`{:error, r}`, never as `{:ok, {:error, r}}`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
