Packages

A driver for TypeDB 3.12+ in Elixir, built on the TypeDB HTTP API. Databases, users, transactions, TypeQL queries and typed concept answers.

Current section

Files

Jump to
typedb CHANGELOG.md
Raw

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.2] - 2026-08-01
Evidence. Every number this file publishes is now produced by a script in the
repository, the supported TypeDB floor was measured instead of assumed, and the
tests that found the three fixes below did not exist a release ago. No public
API changed — `test/api_snapshot.txt` is byte for byte the one 0.2.1 shipped.
### Added
- A bounded concurrency soak in the integration suite: 200 concurrent reads,
100 concurrent writes checked for exactly-once landing, and 25 concurrent
transactions. The numbers the CHANGELOG has published since 0.1.0 were
produced by hand; these run on every push.
- A coverage floor, enforced by CI.
- A fault-injection matrix: thirteen ways an adapter or a server can misbehave,
against every public call that reaches one, asserting that each produces a
`%TypeDB.Error{}` and leaves the connection alive.
- Property-based round-trip tests over the wire boundary — `TypeDB.Duration`,
`TypeDB.DateTimeTZ`, `TypeDB.Given` and `TypeDB.Concept` — where every
subtle bug in this driver has actually been. `stream_data` is a test-only
dependency and does not reach the package.
- `bench/decode.exs` and `bench/transport.exs`, the scripts behind the numbers
quoted here. They were previously run by hand from a scratch directory that
no longer exists, which made every figure in this file unfalsifiable.
### Changed
- **The supported TypeDB floor is 3.12.0, and it is now a measured one.** The
suite was run down the published releases rather than reasoned about: 3.12.0
passes whole, 3.11.5 fails fourteen integration tests, and 3.5.0 fails more
broadly still. TypeQL's `given` stage — the driver's answer to query
injection — is a syntax error before 3.12, so every parameterised query fails
there, and `User.delete/2` on an unknown user answers 400 rather than 404.
3.12.0 is in CI's integration matrix beside 3.12.1 and `latest`, so the claim
stays true. The README said "3.12 or newer" before this and happened to be
right; it was not evidence.
- Re-measured throughput, on the scripts now in `bench/`, against a local
TypeDB in this project's container — 400 requests per run, warm pool, a
one-row `match`. At 200-way concurrency Finch sustains ~1820 req/s and Req
~1640, both with a p99 under 130ms; `:httpc` manages ~375 req/s at a p99 of
685ms. The README's table is these numbers, and now names the script that
produced them. 0.1.0 published 77 req/s for `:httpc` at 200-way, which did
not reproduce; the ratio is the finding and it holds, the absolute numbers
are a property of whatever machine you run them on.
### Fixed
- `TypeDB.Transaction.analyze/3` returned `{:ok, :ok}` for a 200 with an empty
body, where its spec promises `{:ok, map()}`. It now rejects a payload that is
not a structure. Found by the fault matrix. (`analyze/3`'s return is the one
documented SemVer exemption, which is why this is a patch.)
- `TypeDB.Duration.parse/1` ran a regular expression once per component, and
its trailing `(.*)` copied the rest of the string each time. It cost 16µs a
duration where every other cast costs under half a microsecond. Scanning the
number's length and slicing brings it to 1.6µs — ten times faster, for the
same values: checked by re-parsing 200,000 generated durations, well-formed
and malformed, through both implementations.
- `TypeDB.Concept.cast/2` asked `Code.ensure_loaded?(Decimal)` once per value.
For an application that *has* `Decimal` that is a cached lookup costing
nothing; for one that does not it is a code-server round trip, and it was the
entire cost of casting a decimal — 22µs a value, against 0.4µs with the
dependency present. The answer is now memoised in `:persistent_term`, which
takes 50,000 casts without `Decimal` from 1099ms to 5ms.
## [0.2.1] - 2026-08-01
Documentation only. The single change under `lib/` is six lines of moduledoc;
`test/api_snapshot.txt` is byte for byte the one 0.2.0 shipped.
These were written after 0.2.0 was tagged and were briefly listed under it in
this file, which was wrong: a published release does not grow.
### Added
- Four guides, published with the docs and shipped in the package:
[Transactions](guides/transactions.md),
[Errors and retries](guides/errors-and-retries.md),
[Telemetry and logging](guides/observability.md), and
[Testing an application](guides/testing.md).
- A Livebook notebook, with a Run in Livebook badge on the README: a database, a
schema, reads and writes, a parameterised query that survives a hostile value,
and a transaction. Its code blocks are parsed by the test suite, and the
version it installs is checked against this project.
- A Limitations section in the README: answers arrive whole, a connection points
at one server, retries block the caller, one connection is one HTTP pool.
### Changed
- The documentation's module groups are ordered for reading rather than by
accretion, and a test now asserts every published module is filed under
exactly one of them.
## [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.2...HEAD
[0.2.2]: https://github.com/NoeticEcho/TypedbEx/compare/v0.2.1...v0.2.2
[0.2.1]: https://github.com/NoeticEcho/TypedbEx/compare/v0.2.0...v0.2.1
[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