TypeDB (TypeDB v0.2.0)

Copy Markdown View Source

A driver for TypeDB 3.12 and newer, built on the TypeDB HTTP API.

Older 3.x is not supported; see the README for what breaks and why.

Getting started

Add the connection to your supervision tree:

children = [
  {TypeDB,
   url: "http://localhost:8000",
   username: "admin",
   password: System.fetch_env!("TYPEDB_PASSWORD")}
]

Supervisor.start_link(children, strategy: :one_for_one)

Then query. TypeDB is the default connection name:

TypeDB.query!(TypeDB, "social", """
  match
    $p isa person, has name $name;
  select $name;
""")
|> Enum.map(&TypeDB.ConceptRow.typed_value(&1, "name"))
#=> ["Alice", "Bob"]

The two ways to run a query

query/4 runs a single query in its own transaction — TypeDB opens it, runs the query, and commits or closes it in one round trip. Reads never commit; writes and schema changes commit by default, which commit: false turns off. Every function takes the connection's registered name as its first argument; conn below is that name, TypeDB unless you chose another.

TypeDB.query(conn, "social", "insert $p isa person, has name 'Alice';")

transaction/5 opens a transaction you can run several queries in, committing once at the end:

TypeDB.transaction(conn, "social", :write, fn tx ->
  TypeDB.Transaction.query!(tx, "insert $p isa person, has name 'Alice';")
  TypeDB.Transaction.query!(tx, "insert $p isa person, has name 'Bob';")
end)

The block commits on success and rolls back on error or exception. A :read block never commits.

Answers

Every query returns a TypeDB.Answer — see that module for the three shapes and how to consume them.

Errors

Every function in this module and in TypeDB.Database, TypeDB.User, TypeDB.Server and TypeDB.Transaction that can fail has both a {:ok, _} | {:error, %TypeDB.Error{}} form and a ! form that raises. TypeDB.Error carries TypeDB's own error :code, which is what you want to branch on.

The one exception is transaction/5: it returns whatever your block returned, so its {:error, _} may be your own value rather than an exception, and a bang form would have to guess whether to raise it.

How long a call can take

Four options interact, and only the last of them bounds the call rather than one attempt. With the defaults:

defaultwhat it bounds
:connect_timeout10_000opening the socket
:timeout60_000waiting for one response
:max_retries1how many extra attempts
:retry_max_delay5_000one wait between attempts
:deadline:infinitythe whole call

So a retryable call costs at worst

(max_retries + 1) * (connect_timeout + timeout) + max_retries * retry_max_delay

which is a little over two minutes by default, plus one more connect_timeout + timeout if the call has to mint a token first. Raising :max_retries multiplies the first term — that is the arithmetic :deadline exists for, since setting it makes the whole expression irrelevant: no call outlives its budget, each attempt is given only what the budget has left, and a retry that could not finish inside it is not started.

Retries and their backoffs happen in the calling process. Nothing is queued behind a connection, but nothing is running in the caller either.

Logging

The driver logs sparingly, and nothing at all on the happy path — use TypeDB.Telemetry for that. This is everything it can say:

levelwhen
debuga request is about to be retried
debuga connection process received a message it does not understand
warningretries were exhausted and the call is giving up
warninga token renewal failed
errora connection's transport died and the connection is stopping

TypeDB.HTTP.Httpc additionally warns once at start-up when it can find no OS trust store, carrying :typedb_adapter rather than :typedb_connection: it happens while the adapter is being built, before there is a connection to name or a :log_level to consult.

Set :log_level on the connection to raise the floor, or :none to silence it — see TypeDB.Config. It applies per connection, so one noisy connection can be quietened without filtering the whole application's Logger by module.

Every line carries :typedb_connection in its Logger metadata, and the retry and give-up lines additionally carry :typedb_method, :typedb_path, :typedb_attempt and :typedb_error_kind. Configure your backend to keep them:

config :logger, :default_formatter,
  metadata: [:typedb_connection, :typedb_error_kind]

Credentials never appear in a log line or in metadata; see TypeDB.Config for how the connection keeps them out of crash reports too.

Concurrency

Requests run in the calling process, so N processes issue N concurrent requests; the connection process is consulted only to mint or renew the auth token. Sockets are pooled by the HTTP adapter — :max_sessions on TypeDB.HTTP.Httpc caps how many are opened per host.

What this driver covers

Everything in the TypeDB HTTP API v1: sign-in and token renewal, databases, users, servers, version and health, explicit transactions, one-shot queries, and query analysis. TypeDB's gRPC-only features — database import/export and streaming answers — are not available over HTTP and so are not here.

Summary

Types

A connection: the registered name of a TypeDB.Connection process.

Functions

Creates a database, raising on failure. See TypeDB.Database.create!/2.

Lists databases. See TypeDB.Database.list/1.

Lists databases, raising on failure. See TypeDB.Database.list!/1.

Deletes a database and all of its data. See TypeDB.Database.delete/2.

Deletes a database, raising on failure. See TypeDB.Database.delete!/2.

Returns :ok when the server is reachable. See TypeDB.Server.health/1.

Returns :ok when the server is reachable, raising otherwise. See TypeDB.Server.health!/1.

Runs a single query in a transaction of its own.

Runs a single query, raising TypeDB.Error on failure.

Starts a connection. See TypeDB.Config for options.

Stops a connection, by registered name or pid.

Runs fun inside a transaction, committing on success.

Returns the server distribution and version. See TypeDB.Server.version/1.

Returns the server distribution and version, raising on failure. See TypeDB.Server.version!/1.

Types

conn()

@type conn() :: TypeDB.Connection.t()

A connection: the registered name of a TypeDB.Connection process.

Functions

create_database(conn, name)

@spec create_database(conn(), String.t()) :: :ok | {:error, TypeDB.Error.t()}

Creates a database. See TypeDB.Database.create/2.

create_database!(conn, name)

@spec create_database!(conn(), String.t()) :: :ok

Creates a database, raising on failure. See TypeDB.Database.create!/2.

databases(conn)

@spec databases(conn()) :: {:ok, [String.t()]} | {:error, TypeDB.Error.t()}

Lists databases. See TypeDB.Database.list/1.

databases!(conn)

@spec databases!(conn()) :: [String.t()]

Lists databases, raising on failure. See TypeDB.Database.list!/1.

delete_database(conn, name)

@spec delete_database(conn(), String.t()) :: :ok | {:error, TypeDB.Error.t()}

Deletes a database and all of its data. See TypeDB.Database.delete/2.

delete_database!(conn, name)

@spec delete_database!(conn(), String.t()) :: :ok

Deletes a database, raising on failure. See TypeDB.Database.delete!/2.

health(conn)

@spec health(conn()) :: :ok | {:error, TypeDB.Error.t()}

Returns :ok when the server is reachable. See TypeDB.Server.health/1.

health!(conn)

@spec health!(conn()) :: :ok

Returns :ok when the server is reachable, raising otherwise. See TypeDB.Server.health!/1.

query(conn, database, query, opts \\ [])

@spec query(conn(), String.t(), String.t(), keyword()) ::
  {:ok, TypeDB.Answer.t()} | {:error, TypeDB.Error.t()}

Runs a single query in a transaction of its own.

TypeDB opens the transaction, runs the query, then commits or closes it — one HTTP round trip in total, which makes this the cheapest way to run a standalone query.

Options

  • :transaction_type:read, :write or :schema. Defaults to :schema, the only type that accepts every kind of query.

    A :schema transaction takes TypeDB's exclusive, database-wide schema lock for the duration of the call. One-shot queries left on the default therefore serialise against each other, and against anything else holding that lock. Pass transaction_type: :read for reads and :write for data changes — both are concurrent — and leave the default to define and undefine. Narrowing also has the server reject an accidental write.

  • :commit — whether to commit a write or schema query. Defaults to true. Read queries never commit.

  • :given_rows — input rows for the query's given stage; see TypeDB.Transaction.query/3 for how to parameterise a query safely.

  • plus all query and transaction options from TypeDB.Options, and :timeout and :deadline — the first bounds one attempt, the second the whole call including retries. See TypeDB.Config.

Raises

Unlike the rest of this module, query/4 has two failure paths. Anything the server rejects comes back as {:error, %TypeDB.Error{}}; anything rejected before the request is built raises, because there is no request to fail:

  • ArgumentError for an invalid :transaction_type — that is a literal in your source, not data.
  • TypeDB.Error with kind :encode for a :given_rows value the driver cannot turn into a TypeDB wire value, including a negative TypeDB.Duration.

Examples

TypeDB.query(conn, "social", "match $p isa person; select $p;",
  transaction_type: :read,
  answer_count_limit: 100
)

# Dry run: execute the write, then throw it away.
TypeDB.query(conn, "social", "insert $p isa person;", commit: false)

# Parameterised, and therefore safe against TypeQL injection.
TypeDB.query(conn, "social", """
  given $n: string;
  insert $p isa person, has name == $n;
""", given_rows: [%{"n" => user_supplied_name}])

query!(conn, database, query, opts \\ [])

@spec query!(conn(), String.t(), String.t(), keyword()) :: TypeDB.Answer.t()

Runs a single query, raising TypeDB.Error on failure.

start_link(opts)

@spec start_link(keyword()) :: GenServer.on_start()

Starts a connection. See TypeDB.Config for options.

stop(conn, reason \\ :normal, timeout \\ :infinity)

@spec stop(conn() | pid(), term(), timeout()) :: :ok

Stops a connection, by registered name or pid.

transaction(conn, database, type, fun, opts \\ [])

@spec transaction(
  conn(),
  String.t(),
  TypeDB.Transaction.type(),
  (TypeDB.Transaction.t() -> result),
  keyword()
) :: result | {:error, TypeDB.Error.t()}
when result: term()

Runs fun inside a transaction, committing on success.

The transaction is committed when fun returns, rolled back when it returns {:error, _}, and rolled back before the exception propagates when it raises, throws or exits. :read transactions are closed rather than committed, since there is nothing to commit.

Returns whatever fun returned, except that a successful :write/:schema block whose commit fails returns {:error, %TypeDB.Error{}}.

Options

Transaction options from TypeDB.Options, plus :timeout and :deadline, which are forwarded to the request that opens the transaction.

Examples

TypeDB.transaction(conn, "social", :write, fn tx ->
  TypeDB.Transaction.query!(tx, "insert $p isa person, has name 'Alice';")
  TypeDB.Transaction.query!(tx, "insert $p isa person, has name 'Bob';")
  :ok
end)
#=> :ok

# Returning {:error, _} rolls back and returns that error unchanged.
TypeDB.transaction(conn, "social", :write, fn tx ->
  case TypeDB.Transaction.query(tx, "insert $p isa person;") do
    {:ok, _} -> {:error, :changed_my_mind}
    error -> error
  end
end)
#=> {:error, :changed_my_mind}

version(conn)

@spec version(conn()) :: {:ok, TypeDB.Server.version()} | {:error, TypeDB.Error.t()}

Returns the server distribution and version. See TypeDB.Server.version/1.

version!(conn)

@spec version!(conn()) :: TypeDB.Server.version()

Returns the server distribution and version, raising on failure. See TypeDB.Server.version!/1.