# `DrinkMeAlice`

A bounded SurrealDB client for Elixir, qualified against SurrealDB
3.0.5 (see the README's Compatibility section for the version policy
and the cleartext-transport deployment boundary).

Layers, bottom up:

- `DrinkMeAlice.Ws` / `FrameGuard` / `Json` — Mint WebSocket
  transport with frame bounds and a bounded JSON decode profile.
- `DrinkMeAlice.Conn` — one supervised connection owner with
  credit-bounded admission, deadlines and honest transport facts.
- `DrinkMeAlice.Session` — a caller-owned, synchronous socket for
  short-lived work (scripts, provisioning, migrations).
- `DrinkMeAlice.Outcome` + `DrinkMeAlice.Tx` — transaction scripts
  and the reply classifier that never collapses `unknown` into
  `rejected` or `committed`.
- `DrinkMeAlice.Admin` + `DrinkMeAlice.Migrations` — root-only
  provisioning and a checksummed, locked migration runner.
- `DrinkMeAlice.Ash.DataLayer` — an Ash data layer (compiled only
  when the consumer brings Ash).

This module holds the read-side helper `query/4` and the shared
identifier check.

# `check_param_names`

```elixir
@spec check_param_names(map()) :: :ok | {:error, {:protected_param, binary()}}
```

SurrealDB 3.0.5 protects the parameter names `$access`, `$auth`, `$session`, `$token`:
binding one makes the server refuse the WHOLE request (JSON-RPC -32600,
measured). `:ok`, or `{:error, {:protected_param, name}}` before sending.
Only atom and binary key names are inspected; other key types are
rejected later by the outbound encoder with its own typed error, so
this check never raises on them.

# `exact_string`

```elixir
@spec exact_string(binary()) :: binary()
```

Wire value for an EXACT string parameter — pair it with `exact_string_sql/1`.

SurrealDB 3.0.5's JSON-RPC parameter conversion coerces strings that
merely look typed — at any depth, inside arrays and objects too:

| sent               | arrives as          | `<string>` of it            |
|--------------------|---------------------|-----------------------------|
| `"2026-10-01"`     | `datetime`          | `"2026-10-01T00:00:00Z"` ✗  |
| `"task:abc"`       | record id           | `"task:abc"`                |
| UUID-shaped string | `uuid`              | the same string             |

A `<string>$p` cast repairs the UUID
and record-id cases but NOT date-shaped strings, which come back
rewritten. Prefixing one character that no coercion recognises and
slicing it off server-side is exact for every string (verified against
the pinned server, including the empty string and non-ASCII text).

# `exact_string_list_sql`

```elixir
@spec exact_string_list_sql(binary()) :: binary()
```

The SurrealQL expression that recovers a list of `exact_string/1` values in param `name`.

# `exact_string_sql`

```elixir
@spec exact_string_sql(binary()) :: binary()
```

The SurrealQL expression that recovers a `exact_string/1` param named `name`.

# `identifier?`

```elixir
@spec identifier?(term()) :: boolean()
```

True for a SurrealQL identifier this connector will splice into SQL:
`[A-Za-z_][A-Za-z0-9_]*`, at most 64 bytes. Everything
else travels as a bound `$param`.

# `query`

```elixir
@spec query(GenServer.server(), binary(), map(), keyword()) ::
  {:ok, list()} | {:error, term()}
```

Run one read-side `query` through a `Conn` owner.

Returns `{:ok, results}` where `results` is the per-statement array
(`%{"status" => "OK" | "ERR", "result" => _}` each — callers check
every status), or `{:error, reason}`:

- `{:rpc_error, code}` / `:session_refused` — the server refused;
- `{:lost, reason}` — sent, no reply (for a write this is an UNKNOWN
  outcome, never a failure — use `DrinkMeAlice.Tx.run/4` for writes);
- `{:refused, reason}` — certified never sent (`:down`, `:expired`,
  `:exhausted`, `:too_large`, `:encode`).

Options: `deadline_ms` (≤ 2_000), `class` (`:general` | `:internal`).

# `stringify_param_names`

```elixir
@spec stringify_param_names(map()) :: map()
```

Normalize a query's parameter map: atom names become strings (the
outbound encoder (`DrinkMeAlice.Json.encode/1`) accepts only
string keys). Values must already be canonical-domain terms — binaries,
i64 integers, booleans, `nil`, lists and string-keyed maps; floats,
decimals and datetimes travel as strings with a SurrealQL cast.

---

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