The transport seam (charter D2). One implementation ships — Arcadic.Transport.HTTP
(Req/Finch). A future Bolt adapter implements the same callbacks. Callbacks are
SEMANTIC (mode/session), not HTTP-verb-shaped, so a non-HTTP transport can honor
them. This behaviour is also the mock seam consumed by ash_arcadic's tests.
request is the logical statement bundle %{statement: String.t(), params: map(), language: String.t()}; the transport shapes its own wire format from it.
Summary
Callbacks
Bulk-ingest NDJSON vertex/edge records (POST /api/v1/batch/<db>). The body is already-serialized
NDJSON iodata; opts carries the query params (:light_edges, :commit_every). Edge endpoints
resolve by the vertex @id temp key in the body, not a query param.
Optional — HTTP implements it; Bolt has no batch endpoint. Returns the parsed
{verticesCreated, edgesCreated, elapsedMs, idMapping} map.
Begin a session transaction; returns the session id.
Commit the session carried by conn.session_id.
Create a single record of type from a property map (gRPC CreateRecord). Returns the @rid.
Whether a database exists.
Database-level info for conn.database — %{database, type, records, classes, size_bytes}. Fields a
transport can't cheaply provide are nil (HTTP's schema:database gives name/size but not record/class
counts; gRPC GetDatabaseInfo gives all). Optional — HTTP and gRPC implement it.
Delete a single record by rid (gRPC DeleteRecord).
Run a read (:read → idempotent endpoint) or write (:write) statement.
Fire-and-forget write (server enqueues, does not await). Optional — HTTP implements it.
Like execute/4 for a read/write but also returns the server's HA commit index
(X-ArcadeDB-Commit-Index response header) as {:ok, rows, index} — index is
nil on a single (non-HA) server where the header is absent. Optional — HTTP
implements it; Bolt has no HA header. Used by query_bookmarked/command_bookmarked.
Run an EXPLAIN/PROFILE statement and return its plan. The request.statement already
carries the EXPLAIN/PROFILE prefix (the facade prepends it). Optional. Returns
%{plan: <human string>, plan_tree: <raw, transport-defined map>, rows: <executed rows>}.
Liveness probe (GET /api/v1/health → 204).
Bulk-insert rows (property maps) into target_class — document/table ingest (gRPC BulkInsert),
distinct from batch_ingest's graph vertex/edge batch. opts carries :conflict_mode/:key_columns.
Returns the InsertSummary-shaped counts map (per-row errors surfaced value-free as %{row_index, code}).
Optional — gRPC implements it; HTTP/Bolt do not.
List all database names (GET /api/v1/databases).
Mint a session token from the conn's credentials (POST /api/v1/login).
Revoke the current session (POST /api/v1/logout).
Look up a single record by rid (gRPC LookupByRid). {:ok, map} or {:ok, nil} if absent.
Lazily stream a large read result as raw row maps (Bolt cursor, or HTTP @rid offset paging). Optional.
Server readiness.
Roll back the session carried by conn.session_id.
Run a server-level admin command (create/drop database).
Authenticated admin GET returning a decoded JSON map (HTTP-only admin surface).
Native fun-based transaction (for transports whose sessions are not detachable, e.g. Bolt). Optional.
Fetch the newest point (GET /api/v1/ts/<db>/latest). params is a [{String.t(), String.t()}]
query-param list (type required; at most one tag — the server applies only the FIRST, probed).
Returns %{columns, latest} (atomized). Optional — HTTP-only.
PromQL read family (GET /api/v1/ts/<db>/prom/api/v1/…). op is the allowlisted route atom —
:query | :query_range | :labels | :series | {:label_values, label} (the label is
facade-validated AND URL-encoded here). Unwraps the Prometheus envelope: status:"success" →
{:ok, data}; status:"error" → a typed error. Optional — HTTP-only.
Run a time-series query (POST /api/v1/ts/<db>/query). body is the already-shaped wire map.
Returns the RAW %{columns, rows, count} or AGGREGATED %{aggregations, buckets, count} shape
(atomized top-level keys; bucket maps atomized to %{timestamp, values}). Optional — HTTP-only.
Write raw InfluxDB line protocol to POST /api/v1/ts/<db>/write (204 on success). lines is
already-built line-protocol iodata; opts[:precision] is the validated "ns"|"us"|"ms"|"s"
string (the FACADE validates — an invalid value is silently ignored server-side, probed 26.7.2).
Append-only, no dedup: a lost response + naive retry duplicates every point. Optional — HTTP-only.
Update a single record by rid (gRPC UpdateRecord; partial merge, or replace: true).
Types
@type plan_result() :: {:ok, %{plan: String.t(), plan_tree: map(), rows: [map()]}} | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
@type result() :: {:ok, [map()]} | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Callbacks
@callback batch_ingest(Arcadic.Conn.t(), ndjson :: iodata(), opts :: keyword()) :: {:ok, map()} | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Bulk-ingest NDJSON vertex/edge records (POST /api/v1/batch/<db>). The body is already-serialized
NDJSON iodata; opts carries the query params (:light_edges, :commit_every). Edge endpoints
resolve by the vertex @id temp key in the body, not a query param.
Optional — HTTP implements it; Bolt has no batch endpoint. Returns the parsed
{verticesCreated, edgesCreated, elapsedMs, idMapping} map.
@callback begin(Arcadic.Conn.t(), opts :: keyword()) :: {:ok, String.t()} | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Begin a session transaction; returns the session id.
@callback commit(Arcadic.Conn.t()) :: :ok | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Commit the session carried by conn.session_id.
@callback create_record( Arcadic.Conn.t(), type :: String.t(), props :: map(), opts :: keyword() ) :: {:ok, String.t()} | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Create a single record of type from a property map (gRPC CreateRecord). Returns the @rid.
@callback database_exists?(Arcadic.Conn.t(), name :: String.t()) :: {:ok, boolean()} | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Whether a database exists.
@callback database_info(Arcadic.Conn.t()) :: {:ok, map()} | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Database-level info for conn.database — %{database, type, records, classes, size_bytes}. Fields a
transport can't cheaply provide are nil (HTTP's schema:database gives name/size but not record/class
counts; gRPC GetDatabaseInfo gives all). Optional — HTTP and gRPC implement it.
@callback delete_record(Arcadic.Conn.t(), rid :: String.t(), opts :: keyword()) :: :ok | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Delete a single record by rid (gRPC DeleteRecord).
@callback execute(Arcadic.Conn.t(), mode :: :read | :write, request(), opts :: keyword()) :: result()
Run a read (:read → idempotent endpoint) or write (:write) statement.
@callback execute_async(Arcadic.Conn.t(), request(), opts :: keyword()) :: :ok | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Fire-and-forget write (server enqueues, does not await). Optional — HTTP implements it.
@callback execute_with_index( Arcadic.Conn.t(), mode :: :read | :write, request(), opts :: keyword() ) :: {:ok, [map()], integer() | nil} | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Like execute/4 for a read/write but also returns the server's HA commit index
(X-ArcadeDB-Commit-Index response header) as {:ok, rows, index} — index is
nil on a single (non-HA) server where the header is absent. Optional — HTTP
implements it; Bolt has no HA header. Used by query_bookmarked/command_bookmarked.
@callback explain(Arcadic.Conn.t(), request(), opts :: keyword()) :: plan_result()
Run an EXPLAIN/PROFILE statement and return its plan. The request.statement already
carries the EXPLAIN/PROFILE prefix (the facade prepends it). Optional. Returns
%{plan: <human string>, plan_tree: <raw, transport-defined map>, rows: <executed rows>}.
@callback health?(Arcadic.Conn.t()) :: {:ok, boolean()} | {:error, Arcadic.TransportError.t()}
Liveness probe (GET /api/v1/health → 204).
@callback insert_rows( Arcadic.Conn.t(), target_class :: String.t(), rows :: [map()], opts :: keyword() ) :: {:ok, map()} | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Bulk-insert rows (property maps) into target_class — document/table ingest (gRPC BulkInsert),
distinct from batch_ingest's graph vertex/edge batch. opts carries :conflict_mode/:key_columns.
Returns the InsertSummary-shaped counts map (per-row errors surfaced value-free as %{row_index, code}).
Optional — gRPC implements it; HTTP/Bolt do not.
@callback list_databases(Arcadic.Conn.t()) :: {:ok, [String.t()]} | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
List all database names (GET /api/v1/databases).
@callback login(Arcadic.Conn.t()) :: {:ok, String.t()} | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Mint a session token from the conn's credentials (POST /api/v1/login).
@callback logout(Arcadic.Conn.t()) :: :ok | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Revoke the current session (POST /api/v1/logout).
@callback lookup_record(Arcadic.Conn.t(), rid :: String.t(), opts :: keyword()) :: {:ok, map() | nil} | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Look up a single record by rid (gRPC LookupByRid). {:ok, map} or {:ok, nil} if absent.
@callback query_stream(Arcadic.Conn.t(), request(), opts :: keyword()) :: {:ok, Enumerable.t()} | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Lazily stream a large read result as raw row maps (Bolt cursor, or HTTP @rid offset paging). Optional.
@callback ready?(Arcadic.Conn.t()) :: {:ok, boolean()} | {:error, Arcadic.TransportError.t()}
Server readiness.
@callback rollback(Arcadic.Conn.t()) :: :ok | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Roll back the session carried by conn.session_id.
@callback server_command(Arcadic.Conn.t(), command :: String.t()) :: {:ok, map()} | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Run a server-level admin command (create/drop database).
@callback server_get(Arcadic.Conn.t(), path :: String.t()) :: {:ok, map()} | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Authenticated admin GET returning a decoded JSON map (HTTP-only admin surface).
@callback transaction(Arcadic.Conn.t(), (Arcadic.Conn.t() -> result), opts :: keyword()) :: {:ok, result} | {:error, term()} when result: var
Native fun-based transaction (for transports whose sessions are not detachable, e.g. Bolt). Optional.
@callback ts_latest( Arcadic.Conn.t(), params :: [{String.t(), String.t()}], opts :: keyword() ) :: {:ok, map()} | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Fetch the newest point (GET /api/v1/ts/<db>/latest). params is a [{String.t(), String.t()}]
query-param list (type required; at most one tag — the server applies only the FIRST, probed).
Returns %{columns, latest} (atomized). Optional — HTTP-only.
@callback ts_prom_get( Arcadic.Conn.t(), op :: atom() | {:label_values, String.t()}, params :: [{String.t(), String.t()}], opts :: keyword() ) :: {:ok, map() | list()} | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
PromQL read family (GET /api/v1/ts/<db>/prom/api/v1/…). op is the allowlisted route atom —
:query | :query_range | :labels | :series | {:label_values, label} (the label is
facade-validated AND URL-encoded here). Unwraps the Prometheus envelope: status:"success" →
{:ok, data}; status:"error" → a typed error. Optional — HTTP-only.
@callback ts_query(Arcadic.Conn.t(), body :: map(), opts :: keyword()) :: {:ok, map()} | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Run a time-series query (POST /api/v1/ts/<db>/query). body is the already-shaped wire map.
Returns the RAW %{columns, rows, count} or AGGREGATED %{aggregations, buckets, count} shape
(atomized top-level keys; bucket maps atomized to %{timestamp, values}). Optional — HTTP-only.
@callback ts_write(Arcadic.Conn.t(), lines :: iodata(), opts :: keyword()) :: :ok | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Write raw InfluxDB line protocol to POST /api/v1/ts/<db>/write (204 on success). lines is
already-built line-protocol iodata; opts[:precision] is the validated "ns"|"us"|"ms"|"s"
string (the FACADE validates — an invalid value is silently ignored server-side, probed 26.7.2).
Append-only, no dedup: a lost response + naive retry duplicates every point. Optional — HTTP-only.
@callback update_record( Arcadic.Conn.t(), rid :: String.t(), props :: map(), opts :: keyword() ) :: :ok | {:error, Arcadic.Error.t() | Arcadic.TransportError.t()}
Update a single record by rid (gRPC UpdateRecord; partial merge, or replace: true).