Which client, and what they share
Five clients in five languages, one specification, one conformance corpus — and the one page that says where each package comes from.
There are five clients, and none of them is a wrapper around another. Each was written from the protocol specification alone, and each is checked against the same conformance corpus — the one produced by a separate implementation, which is the point: a codec that is wrong in the same way on both sides round-trips perfectly, so a client's own tests cannot catch what the corpus catches.
| language | package | transports |
|---|---|---|
| Rust | tessaridb-client | wire · HTTP |
| Python | tessaridb-client | wire · HTTP |
| TypeScript / Node | @tessaridb/client | wire · HTTP |
| Go | github.com/tessaridb/tessaridb-sdk-go | wire · HTTP |
| Kotlin / JVM | com.tessaridb:tessaridb-client | wire · HTTP |
Every one of them is Apache-2.0. The engine is BUSL-1.1, and the two names are deliberately different so that adding a client brings no engine terms with it.
Where the packages come from today
| client | install | published |
|---|---|---|
| Rust | cargo add tessaridb-client | crates.io, at 0.10.0 |
| Go | go get github.com/tessaridb/tessaridb-sdk-go | no registry exists |
| Python | pip install tessaridb-client | not yet |
| TypeScript | npm install @tessaridb/client | not yet |
| Kotlin | implementation("com.tessaridb:tessaridb-client") | not yet |
Every client is at 0.10.0 in its repository, tagged v0.10.0 on main.
Go is the good kind of exception: Go has no package registry, a module is fetched from the path that names it, so the command above works now and nothing was ever published.
The three that say not yet carry the same install command you will use — and, on their own page, the one line that installs the same code from the repository in the meantime. Nothing on these pages describes an API that does not exist; only the fetch is ahead of itself, and it says so where you would meet it.
A client's version is its own
None of these numbers tracks the engine's, and that is deliberate: a fix in a client would otherwise force an invented engine release, and an engine release would force five invented client releases.
What has to match is the protocol. Every client at 0.10.0 speaks protocol
1.4: it narrows a feed by a condition and reads its progress
(minor 4, sent only to a node that announces minor 4, node 0.33.0-beta and later), reads a refusal's
class (minor 3), reads the redirect frame of minor 1 and follows
it, and sends the
vault frame of minor 2 only to a node that announces minor 2 (node 0.17.0-beta
and later). It connects to any node of protocol major 1 — checked in the
greeting before anything else is sent, so a mismatch is refused there rather than
discovered mid-conversation, where it arrives as a decode failure that reads like
corruption. A differing minor is not a refusal; the node's minor is reported so
a caller can decline to send what an older node cannot read.
Two transports, and the choice is not about taste
The wire protocol is the typed one. A value travels in the store's own
codec, so an integer arrives as a 64-bit integer, a datetime as a datetime and a
geometry as a geometry — none of them narrowed into JSON's six types on the way.
A parameter is an encoded value rather than text, which is what makes a supplied
value unable to become syntax. One connection is one session, so USE NAMESPACE
stays in force.
HTTP is for what HTTP is good at: objects and files, a
backup, /health and /ready, and a script when a socket is inconvenient. It
carries JSON, so it carries JSON's losses: a quoted decimal is indistinguishable
from a string that looks like a number until you know the field's declared kind.
Use the wire for data. Use HTTP for files, backups and probes.
In a browser, which cannot open a TCP socket, the wire protocol travels over a
WebSocket on the HTTP port at GET /wire — the same bytes, so the same types and
the same session. The TypeScript client does
this for you.
What every client refuses to do
- No dialect. None of them puts a query language in front of TessariQL. A layer that replaced it would be a second thing to learn, with its own gaps and its own version.
- No value in the statement text. Every builder binds; only names and directions are interpolated, and each name is checked against a pattern deliberately narrower than the node's own lexer. A name that is not a name is refused rather than quoted into acceptance, because quoting turns a mistake into a statement that runs and means something else.
- No inventing a refusal. A node's refusal is carried through in the node's own words, which already name the place in the script.
- TLS that cannot be switched off once asked for. From
0.8.0every client speaks TLS 1.3 to a node given a certificate, verifies the node's chain and its name, offers no switch that skips either check, and never falls back to the clear after a failed handshake. Without TLS, credentials travel as given, which belongs on a network you protect. See Encryption in transit.