Supporting a language means more than placing a generated client in a repository, because somebody eventually has to install it on an ARM laptop, pass a 64-bit RowId without truncating it through a JavaScript number, decode an Arrow batch, recover a transaction error, and upgrade the engine without discovering that the binding quietly depended on an internal Rust layout; the public count is now 35 languages, but the architecture is still two boundaries, embedded when the runtime can carry the engine safely and HTTP when portability is worth the local round trip.
The full, current package and repository matrix lives on the MongrelDB language clients page. This article is about why the matrix has two tiers instead of pretending every language should load the same native library.
Tier 1 keeps the engine in-process
Nine clients can run MongrelDB inside the application process: Rust, TypeScript and Node.js, Python, C, C++, C# and .NET, Java, Kotlin, and Scala. They reach the engine through one of the native boundaries already supported by their runtime:
- Rust calls the engine crates directly.
- Node.js uses NAPI and returns large identifiers as
BigInt. - Python uses its native extension path.
- C and C++ use the stable C ABI.
- C# and .NET bind the C ABI through their native interop layer.
- Java, Kotlin, and Scala use the JNI shim.
The reward is no HTTP serialization or socket hop, direct access to native query calls, and efficient Arrow IPC movement for columnar results. The cost is packaging: every supported OS and architecture needs the right native artifact or a documented source-build path, and a client release has to stay on the same compatibility train as the engine it embeds.
The Node addon currently publishes prebuilt Linux x64 and arm64 binaries. Other platforms build from source or use RemoteDatabase against the daemon. That caveat belongs next to the install command, not in a troubleshooting page discovered after npm install fails on a release machine.
Tier 2 keeps the language native
Twenty-six clients connect to a running mongreldb-server over HTTP: Clojure, Crystal, D, Dart, Elixir, Erlang, F#, Fortran, Gleam, Go, Julia, Kotlin/Native, Lua, Mojo, Nim, Objective-C, Odin, Perl, PHP, PowerShell, R, Ruby, Swift, Tcl, V, and Zig.
Those packages use the language’s ordinary HTTP and JSON stack, so installing the client does not pull in a Rust compiler, linker, JNI library, platform-specific DLL, or NAPI binary. The daemon owns the database root, keeps the engine warm, enforces sessions and credentials, and supports multiple client processes without asking them to coordinate exclusive filesystem locks.
The cost is real. A local HTTP query crosses request routing, authentication, SQL planning or typed endpoint dispatch, JSON or Arrow serialization, and the loopback network stack. The Stage 1 qualification on the published benchmark host measured a warm loopback SQL point query at 1.287 milliseconds p50, 2.070 milliseconds p95, and 2.396 milliseconds p99; the warm embedded begin/get/rollback path on the same class of evidence measured 1.037 microseconds p50 and 1.434 microseconds p95, though those paths do not perform identical work and should not be presented as a clean protocol-only ratio.
The lesson is smaller than the numbers: use the native boundary when per-call latency and in-process ownership matter, and use the daemon when installation, multi-process access, or language reach matters more.
One C ABI supports several runtimes
The mongreldb-ffi crate exposes opaque handles, typed values, queries, transactions, authentication, DataFusion SQL returning Arrow IPC, and migration planning through a C-compatible boundary. mongreldb-kit-ffi adds schema, migration-runner, and query-builder operations, while mongreldb-jni gives JVM languages a dedicated shim over the same engine behavior.
Opaque handles matter because Rust structs are not an ABI. A C header can remain stable while internal ownership, caches, schedulers, and index implementations change behind it; the binding holds a handle, calls a documented function, receives a documented result, and never depends on the byte layout of an Arc<Database>.
This is less glamorous than generating thirty-five clients from one schema, but it is the difference between a language list and a language contract.
The engine contract stays the same
Both tiers expose the same storage concepts: database, table, schema, typed values, transaction, condition, SQL, and maintenance. The exact convenience layer varies by language, and not every binding has a typed helper for every scored retrieval surface, but the underlying index families and durability model do not change because a caller moved from Rust to PHP.
Remote ranked AI queries use bounded scored endpoints so the server can enforce deadlines, maximum work, candidate ceilings, and concurrency. Embedded code can use direct native conditions where the trusted application owns those limits. This is one of the places where forcing byte-for-byte API symmetry would be worse than preserving the same result semantics with a safer remote boundary.
Choosing a boundary
Use embedded mode when one process can own the storage root, the application can ship the native artifact, and local-call latency matters. Use the daemon when several processes or languages need the same database, when the runtime’s native packaging story is worse than operating one local service, or when centralized authentication and resource limits are required.
A desktop application may embed the engine and inspect it with MongrelDB Viewer. A service fleet may run one authenticated daemon and use language-native HTTP clients. A Node product may embed during local development and use RemoteDatabase in a multi-process deployment. The storage model remains the same, but the ownership boundary changes, which is exactly what a client architecture should make explicit.
Current install names, package status, and repository links are maintained on the 35-language matrix, because counts change and an old article should not be the package registry.