A supervised connection to TypeDB over gRPC.
The same shape as TypeDB.Connection, and for the same reason: the process
owns the channel and the access token, and calls run in the caller's
process. The connection is consulted only when a token has to be minted or
renewed, so it never becomes the throughput bottleneck — which matters more
here than it does over HTTP, since a transaction on this transport is a
long-lived stream and funnelling every message through one process would
serialise every transaction in the VM.
What is published to ETS is the channel and a redacted config. The password never leaves this process.
{:ok, _pid} = TypeDB.GRPC.Connection.start_link(
name: :graph,
address: "127.0.0.1:1729",
username: "admin",
password: "password"
)Opening
The first call on a connection performs connection_open, the RPC every
official driver makes first: it tells the server which protocol version, which
language and which driver version are on this end, and the server answers with
a connection id and the token. So an incompatible driver is refused by the
server at the connection rather than discovered later by something that failed
to decode, and the id in this driver's telemetry is the id in the server's log.
It happens on the first call rather than in start_link/1, which is what keeps
a supervision tree from failing to boot because TypeDB is not up yet.
Tokens
TypeDB issues expiring JWTs, and this driver renews one before it expires by
reading the lifetime out of its claims, exactly as the sibling driver does —
it uses the sibling's reader rather than a second copy of it, which is what
the dependency on typedb is for. A token minted with :token is never
renewed; its expiry surfaces as %TypeDB.Error{kind: :unauthenticated}.
Concurrent renewals collapse: whoever reaches the process first signs in and everyone queued behind takes that token.
Summary
Types
A gRPC channel. GRPC.Channel defines the struct but no t/0, so this names
it here rather than referring to a type that does not exist.
Functions
Runs fun with fresh metadata, renewing the token once if the call comes back
unauthenticated.
The gRPC channel. Safe to use from any process.
The connection's config, with credentials redacted.
The id the server gave this connection, or nil before it has been opened.
Metadata carrying a usable token, for a unary call or a transaction stream.
Renews the access token.
Whether conn can serve a call.
Starts a connection. See TypeDB.GRPC.Config.new/1 for the options.
Stops a connection.
A token that is not about to expire, minting one if needed.
Performs a unary RPC, converting failures into %TypeDB.Error{}.
Types
@type channel() :: %GRPC.Channel{ accepted_compressors: term(), adapter: term(), adapter_payload: term(), codec: term(), compressor: term(), cred: term(), headers: term(), host: term(), interceptors: term(), port: term(), ref: term(), scheme: term() }
A gRPC channel. GRPC.Channel defines the struct but no t/0, so this names
it here rather than referring to a type that does not exist.
@type t() :: atom()
Functions
@spec authenticated(t(), (map() -> {:ok, term()} | {:error, TypeDB.Error.t()})) :: {:ok, term()} | {:error, TypeDB.Error.t()}
Runs fun with fresh metadata, renewing the token once if the call comes back
unauthenticated.
One retry, not a loop: a token this connection has just minted being rejected means the credentials or the clock are wrong, and going round again would only spend another round trip discovering the same thing.
The gRPC channel. Safe to use from any process.
@spec config(t()) :: TypeDB.GRPC.Config.t()
The connection's config, with credentials redacted.
The id the server gave this connection, or nil before it has been opened.
A UUID, and the same one the server writes in its own log, so it is what
connects a slow query on this side to a session on that side. nil until the
first call makes the connection sign in — this driver opens lazily — and
permanently nil for a connection configured with a :token, which has no
credentials to open with.
@spec metadata(t()) :: {:ok, map(), integer()} | {:error, TypeDB.Error.t()}
Metadata carrying a usable token, for a unary call or a transaction stream.
@spec renew_token(t(), :any | integer()) :: {:ok, String.t()} | {:error, TypeDB.Error.t()}
Renews the access token.
minted_before is the monotonic millisecond at which the caller obtained the
token it found wanting, or :any. Passing it is what distinguishes "my token
really is stale" from "somebody already replaced it while I was queued", and
it is why a burst of concurrent 401s costs one sign-in rather than one each.
Whether conn can serve a call.
The same question — and the same caveat — as TypeDB.running?/1: it answers
about this node's connection process, not about TypeDB. A connection whose
server is unreachable is still running.
@spec start_link(keyword()) :: GenServer.on_start()
Starts a connection. See TypeDB.GRPC.Config.new/1 for the options.
Stops a connection.
@spec token(t()) :: {:ok, String.t()} | {:error, TypeDB.Error.t()}
A token that is not about to expire, minting one if needed.
@spec unary( t(), (channel(), map() -> {:ok, term()} | {:error, term()}), String.t(), keyword() ) :: {:ok, term()} | {:error, TypeDB.Error.t()}
Performs a unary RPC, converting failures into %TypeDB.Error{}.
context names the operation for the message when the server supplies
nothing better.