The single exception type raised (or returned) by every TypeDB operation.
Errors carry a :kind describing where the failure came from:
:server— TypeDB answered with a structured error body.:codeholds the TypeDB error code (e.g."TSV11","AUT3","HSR4"),:statusthe HTTP status, and:messagethe server's message, including its cause trace.:transport— the request never produced an HTTP response (connection refused, DNS failure, TLS failure, socket closed).:timeout— the request exceeded the configured timeout.:unauthenticated— credentials were rejected, or the token expired and could not be renewed.:decode— the response body was not valid JSON, or did not match the shape this driver expects.:encode— the mirror of:decode: an Elixir term could not be turned into a TypeDB wire value. Agiven_rowsentry of a type TypeDB has no equivalent for, or aTypeDB.Durationwith a negative component, which TypeQL's grammar cannot express. Raised rather than returned, because it happens while the request is still being built and there is nothing to fail.:config— the driver was configured incorrectly. Raised at start-up.
Matching on TypeDB error codes
case TypeDB.query(conn, "social", "match $x isa nonexistent;") do
{:error, %TypeDB.Error{kind: :server, code: code}} -> {:bad_query, code}
{:ok, answer} -> answer
endError codes are stable across TypeDB releases and are the recommended thing to branch on; messages are not.
Summary
Functions
Builds an error from a TypeDB error response body.
Builds an error.
Whether retrying the call that produced this error could plausibly help.
TypeDB error codes that retryable?/1 treats as worth another attempt,
whatever status they arrive with.
The response statuses the driver treats as retryable by default.
Types
Functions
@spec from_response(pos_integer(), term()) :: t()
Builds an error from a TypeDB error response body.
TypeDB error bodies are %{"code" => code, "message" => message}. Anything
else is reported verbatim so that no information is lost.
Builds an error.
Public because TypeDB.HTTP is a public extension point and TypeDB.HTTP.request/6
is required to return one of these. Application code should be matching on
errors, not constructing them.
TypeDB.Error.new(:transport, "connection refused", reason: :econnrefused)Options
:code— TypeDB's own error code, for:servererrors:status— the HTTP status:reason— the underlying term, whatever it was:body— the response body that could not be understood
Whether retrying the call that produced this error could plausibly help.
case TypeDB.transaction(conn, "social", :write, &steps/1) do
{:error, %TypeDB.Error{} = error} ->
if TypeDB.Error.retryable?(error), do: retry_the_whole_thing(), else: give_up(error)
result ->
result
endNote what this is not for. By the time you are holding an error the driver
has already retried whatever its configuration allowed, so a true here does
not mean it gave up early. This is for the layer above — retrying a whole
transaction, requeueing a job — where the unit of work is bigger than one HTTP
call and the driver could not have retried it for you.
:server errors are judged by retryable_statuses/0 rather than by a
connection's :retry_on_status, because an error does not carry the
connection that produced it — plus retryable_codes/0, which is how an
isolation conflict qualifies despite arriving as a 400 and a vanished
transaction despite arriving as a 404. The first is the reason this function
exists: a commit rejected because a concurrent :write transaction won the
race is exactly the failure a caller is meant to replay.
iex> TypeDB.Error.retryable?(TypeDB.Error.new(:transport, "connection refused"))
true
iex> conflict = TypeDB.Error.new(:server, "isolation conflict", code: "STC2", status: 400)
iex> TypeDB.Error.retryable?(conflict)
true
iex> gone = TypeDB.Error.new(:server, "no open transaction", code: "TSV12", status: 404)
iex> TypeDB.Error.retryable?(gone)
true
iex> TypeDB.Error.retryable?(TypeDB.Error.new(:server, "no such database", status: 404))
false
iex> TypeDB.Error.retryable?(TypeDB.Error.new(:unauthenticated, "bad password"))
false
@spec retryable_codes() :: [String.t()]
TypeDB error codes that retryable?/1 treats as worth another attempt,
whatever status they arrive with.
STC2 is an isolation conflict: two concurrent :write transactions touched
the same data and the loser's commit was rejected. It arrives as a 400,
which is otherwise the driver's signal that a request will fail the same way
forever — and this one will not. Replaying the transaction against the
committed state is the intended response, and the only one available: the
conflict invalidates the whole transaction, so the driver cannot retry it for
you.
TSV12 is "no open transaction", a 404. The transaction the request named
is gone, and nothing it wrote was committed, so replaying it is again both the
right answer and the only one. It arrives when the transaction outlived its
transaction_timeout_millis, when a :timeout or :transport failure made
the driver hang up and TypeDB discarded the transaction with the client, or
when the server restarted underneath it — every one of which is an
operational outcome rather than a statement about the work.
The honest caveat: TSV12 also answers a request made on a transaction the
caller already finished. That is a bug rather than a race, and calling it
retryable means the caller replays, fails the same way, and learns it from the
second failure instead of the first. The trade is deliberate — the cost of
being wrong the other way is a transaction that merely took too long being
reported as permanent, and its work thrown away — and it does not reach
TypeDB.transaction/5, which has no window in which to use a spent handle.
The one case where true is genuinely wrong is a request issued after a
successful commit: there the work did land, and replaying applies it twice.
Note that this is not the same list as :retry_on_status. The driver never
retries a request on one of these codes by itself, because the unit that has
to be retried is bigger than the request.
@spec retryable_statuses() :: [pos_integer()]
The response statuses the driver treats as retryable by default.
429 is a server shedding load; 502, 503 and 504 are what a proxy, an
ingress or a load balancer answers while TypeDB restarts. All four say "not
now" rather than "no". This is the default of :retry_on_status.