Current section
Files
Jump to
Current section
Files
CHANGELOG.md
# Changelog
All notable changes to this project are documented here. The format is based on
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.2.0] - 2026-08-01
Work towards 1.0. Retry and timeout behaviour, observability, and the shape of
the public surface — the three things 1.0 makes irreversible.
### Upgrading from 0.1.0
Four changes can be noticed by working code, and each is deliberate:
1. Backoff delays are now random within their bound. A test that asserted an
exact wait should assert the bound, or pass a function to `:retry_backoff`.
2. More requests are retried — reads, `rollback`, `close`, `Database.create/2`,
`User.set_password/3`, and any response with a status in the new
`:retry_on_status`. A test counting requests to the server may see more of
them. `max_retries: 0` and `retry_on_status: []` restore the old behaviour.
3. `TypeDB.Given` and `TypeDB.Duration.to_iso8601/1` raise `%TypeDB.Error{}`
with kind `:encode` where they raised `:config`. Code matching on
`kind: :config` to catch an unencodable value must match `:encode`.
4. Five documented error codes were wrong and are corrected below. Code
matching on `TSV2`, `TSV3`, `TSV11` or `SRV5` should re-read that entry —
the driver was reporting what its test stub had invented, not what TypeDB
answers.
Everything else is additive.
### Added
- `:retry_max_delay` — a ceiling on any single backoff, whichever form produced
it, including a caller's own `:retry_backoff` function. Defaults to `5_000`;
`:infinity` opts out.
- `:deadline` — a wall-clock budget for a whole call, retries and the waits
between them included. Defaults to `:infinity`. Each attempt is given
whichever is smaller, its own `:timeout` or what the budget has left, and a
retry that could not finish inside the budget is not started. Available per
connection and on every function that already took `:timeout`.
- `:retry_on_status` — statuses to retry in addition to transport failures and
timeouts. Defaults to `[429, 502, 503, 504]`; `[]` opts out. A numeric
`retry-after` is honoured, bounded by `:retry_max_delay`.
- `:log_level` — the quietest level a connection will log at, `:none` to
silence it. Every driver log line now goes through one place, and the
`TypeDB` moduledoc lists all of them.
- `[:typedb, :operation, …]` — a span per call into the public API, with every
retry and token renewal inside it, reporting `:attempts` and a
low-cardinality `:route` safe to use as a metric tag.
- `[:typedb, :transaction, …]` — a span per `TypeDB.transaction/5`, from open
to commit, with an `:outcome` of `:commit`, `:rollback`, `:close` or
`:commit_failed`.
- `[:typedb, :retry, :exhausted]` — emitted when a call stops retrying, with
`:attempts`. The event to alert on.
- `TypeDB.Telemetry.attach_default_logger/1` and `detach_default_logger/0` — a
line per operation, transaction, sign-in and give-up, off unless asked for.
- `:database`, `:transaction_type` and `:transaction_id` in telemetry metadata,
including for `/v1/query`, which carries its database in the request body.
- `TypeDB.Error.retryable?/1` and `TypeDB.Error.retryable_statuses/0` — whether
retrying could plausibly help, for the layer above the driver: retrying a
whole transaction or requeueing a job, where the unit of work is bigger than
one HTTP call. Callers were otherwise copying `kind in [:transport, :timeout]`
out of the driver's internals.
- `TypeDB.ConceptRow.to_struct/2` — builds a struct from a row, raising on a
variable that names no field. `Kernel.struct/2` silently returns the struct's
defaults there, which the `to_map/1` docs previously warned about at length
instead of solving.
- An API snapshot test. `test/api_snapshot.txt` records the whole published
surface and the suite fails when the code and the file disagree, so a SemVer
decision is forced at the moment the API changes rather than at release.
- A versioning policy in CONTRIBUTING: what the version number covers —
telemetry event names and metadata keys, error kinds, the option set and its
defaults, the transport behaviour — and what it does not.
### Changed
- **The default backoff is jittered.** `{:exponential, base}` now draws
uniformly from `0..base * 2 ** (n - 1)` instead of returning that value
exactly, so callers that failed together no longer retry together. Pass a
function to `:retry_backoff` for a delay you can predict.
- **Retry eligibility is decided per operation, not per HTTP method.** Read
queries, opening a `:read` transaction, `analyze`, `rollback`, `close`,
`Database.create/2` and `User.set_password/3` are now retried; writes, schema
changes, `commit`, `User.create/3` and opening a `:write` or `:schema`
transaction are not.
- Retries exhausted and token renewals that fail now log at `:warning`. Both
were silent.
- `TypeDB.Given` and `TypeDB.Duration.to_iso8601/1` raise `%TypeDB.Error{}` with
the new kind **`:encode`** rather than `:config`. `:config` means the driver
was configured wrongly at start-up; these mean an Elixir term has no TypeDB
wire value. `Error.kind()` gains `:encode`.
- CI now compiles and runs the unit suite on Windows through all three HTTP
adapters, so the claim that the driver is pure Elixir is proven rather than
assumed. `mix typedb.check` still wants a POSIX shell there.
- A `decimal` attribute is now stripped of TypeQL's `dec` suffix whether or not
the optional `Decimal` library is loaded. Without it the fallback used to hand
back `"12.345dec"` where the `Decimal` path gave `12.345`, so the value
differed in content, not just in type, depending on which dependencies
happened to be installed. Found by the new optional-dependency CI job.
- **The supported TypeDB range is now stated as 3.12 or newer**, where the
README said "3.x". Measured against 3.5.0, the driver does not work at all
there: `given` rows are rejected, `/v1/servers` does not exist, several error
codes differ and insert-then-match fails. CI runs the integration suite
against `3.12.1` and `latest`. The exact floor between 3.5 and 3.12 is not
established (tdb-vtg.6).
- TypeDB.Transport and TypeDB.Token are internal and no longer published in
the documentation. They were never meant to be called directly.
- **Five error codes were wrong.** Verified against a live TypeDB 3.12.1 and
corrected in the stub, the unit tests and the documentation: opening a
transaction on an unknown database answers `400 SRV3` (not `404 TSV2`);
committing a read transaction answers `400 TSV2` (not `400 TSV3`); any
operation on a finished transaction answers `404 TSV12` (not `404 TSV11`);
`/v1/databases/{name}/schema` on an unknown database answers `404 SRV3` (not
`404 SRV5`); and a one-shot query on an unknown database answers `400 SRV3`.
Code matching on any of the old values must change.
- `TypeDB.transaction/5` no longer rolls back a failed `:read` block. TypeDB
rejects that with `400 TSV3`, so it was a wasted round trip; the transaction
is closed instead, and its telemetry `:outcome` is `:close`.
- `TypeDB.Transaction.open/4` raises `ArgumentError` naming the bad transaction
type and the three accepted ones, where it raised `FunctionClauseError`.
- `Exception.message/1` on a `%TypeDB.Error{}` now includes the HTTP status:
`[server 404] TSV2: Database not found.` The rendered form is what reaches a
log line and an exit reason, where nobody has the struct to inspect. Message
text remains outside SemVer — match on `:kind` and `:code`.
## [0.1.0] - 2026-07-31
Initial release. Complete coverage of the TypeDB HTTP API v1, verified against
TypeDB 3.12.1 on Elixir 1.20 / OTP 29.
### Added
- `TypeDB` — connection supervision, one-shot queries and bracketed transactions.
- `TypeDB.Connection` — lazy sign-in, transparent token renewal bounded by
`:max_auth_renewals`, and per-connection configuration held in a
read-concurrent ETS table so requests run in the caller's process.
- `TypeDB.Database` — list, get, create, create-if-not-exists, delete, schema
and type-schema. `exists?/2` raises rather than answering `false` when it
could not reach the server, since `false` is the answer that makes a caller
create something that already exists.
- `TypeDB.User` — list, get, create, set password, delete.
- `TypeDB.Server` — health, version and cluster membership.
- `TypeDB.Transaction` — explicit `:read`, `:write` and `:schema` transactions
with `query/3`, `analyze/3`, `commit/2`, `rollback/2` and idempotent
`close/2`, each taking its own `:timeout`.
- `TypeDB.Answer` — `Ok`, `ConceptRows` and `ConceptDocuments`; the latter two
are `Enumerable`.
- `TypeDB.ConceptRow` — `Access`-backed rows, plus `value/2`, `typed_value/2` and
`to_map/1`.
- `TypeDB.Concept` — structs for entities, relations, attributes, values and
every type kind, with conversion of TypeDB values to native Elixir terms.
- `TypeDB.Duration` and `TypeDB.DateTimeTZ` — lossless representations of
TypeDB's `duration` and `datetime-tz` values, keeping the original wire string
so TypeDB's nanosecond precision survives conversion to Elixir's coarser
types. `DateTimeTZ.new/2` builds one for writing, from a `NaiveDateTime` plus
an IANA zone name or a UTC offset.
- `TypeDB.Options` — transaction and query options.
- `TypeDB.Given` — encodes input rows for TypeQL's `given` stage into TypeDB's
tagged wire form, making parameterised queries safe against TypeQL injection
for arbitrary input. The API's raw-JSON form is not: TypeDB parses a bare
string as a TypeQL literal, so a value containing a quote is a parse error.
- `TypeDB.Error` — a single exception type carrying TypeDB's stable error codes.
Every function that can fail has both a `{:ok, _} | {:error, %TypeDB.Error{}}`
form and a `!` form that raises, except `TypeDB.transaction/5`, which returns
the block's own value.
- `TypeDB.HTTP` — a transport behaviour with three adapters: `TypeDB.HTTP.Finch`
(the default, a Finch pool per connection), `TypeDB.HTTP.Req` for applications
already running Finch through Req, and `TypeDB.HTTP.Httpc` for deployments that
must run on OTP alone. All three verify TLS by default and are covered by the
same test suite.
- TypeDB.Transport — request building, retries and response decoding, split out
of the connection process.
- TypeDB.Token — reads a token's lifetime from its JWT claims so the driver can
renew before expiry instead of discovering it from a `401`.
- `TypeDB.Telemetry` — `[:typedb, :request, …]` and `[:typedb, :sign_in, …]`
spans. Logging is deliberately sparse and carries `:typedb_connection` in its
Logger metadata; see the "Logging" section of `TypeDB`.
- `TypeDB.JSON` — a codec behaviour resolving to the built-in `JSON`, to `Jason`,
or to a codec you configure.
- `mix typedb.check` — validates `.tql` files with TypeDB's `typeql-check` CLI.
### Verified under load
- 200-way concurrent bursts, concurrent writes and long transactions straddling
token expiry, against servers configured with one- and five-second token
lifetimes: no failures, no lost writes. Renewals coalesce into a single sign-in
per generation, and `:max_auth_renewals` bounds how many times one request will
renew before giving up.
- Transport throughput measured against a local TypeDB 3.12.1, 400 requests per
run: Finch sustains ~1900 req/s at 200-way concurrency where `:httpc` manages
77 with multi-second tail latency. Finch is the default for that reason.
### Verified against
- TypeDB 3.12.1 (HTTP API v1) on Elixir 1.20.2 / OTP 29, including an opt-in
suite that checks the TLS defaults against a server started with
`--server.encryption.enabled`.
[Unreleased]: https://github.com/NoeticEcho/TypedbEx/compare/v0.2.0...HEAD
[0.2.0]: https://github.com/NoeticEcho/TypedbEx/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/NoeticEcho/TypedbEx/releases/tag/v0.1.0