Overview
How one managed engine serves .NET, MAUI, Java, Kotlin, Swift, Flutter, React Native, Python, Node, Go, and C++. Companion pages: bindings.md (SDK how-to), format.md, encryption.md, query.md.
Decision
One engine, one C ABI, many thin SDKs. Storage, WAL, BSON, NQL, Argon2, and AES-256-GCM exist only in Nuventra.NuvexaDB. Other languages do not reimplement the file. They load a Native AOT build of that engine and pass UTF-8 JSON.
A .nvx written from Kotlin is the same file Explorer, Swift, Flutter, and a MAUI app open. Compatibility is a compile of one codebase, not a format treaty between ports.
Non-goals
- A network server or multi-process writer. One process holds
FileShare.Noneon a path. - A second engine in Kotlin, Swift, Dart, or C++.
- LINQ /
GetCollection<T>outside .NET. - SQLite or RocksDB inside
Nuventra.NuvexaDB.
Layers
flowchart TB
subgraph hosts [Host apps]
Maui[".NET / MAUI"]
Jvm["Java / Kotlin"]
Swift["Swift"]
Flutter["Flutter / Dart"]
Rn["React Native"]
Py["Python"]
Node["Node.js"]
Go["Go"]
Cpp["C++"]
Studio["Data Studio / CLI / IDE"]
end
subgraph sdk [Thin SDKs]
Nuget["NuGet Nuventra.NuvexaDB"]
Jna["bindings/jvm JNA"]
Spm["bindings/swift"]
Ffi["bindings/flutter dart:ffi"]
Koffi["bindings/react-native + node"]
Ctypes["bindings/python ctypes"]
Cgo["bindings/go cgo"]
Hpp["bindings/cpp nuvexa.hpp"]
Tools["Tools / ExplorerSession"]
end
subgraph native [Shared native surface]
Header["nuvexa.h"]
Lib["nuvexa.dylib / .so / .dll / xcframework"]
end
subgraph core [Single engine]
Abi["NuvexaAbi + UnmanagedCallersOnly"]
Engine["NuvexaDatabase / pages / WAL / NQL / AES-GCM"]
File["app.nvx"]
end
Maui --> Nuget
Jvm --> Jna
Swift --> Spm
Flutter --> Ffi
Rn --> Koffi
Py --> Ctypes
Node --> Koffi
Go --> Cgo
Cpp --> Hpp
Studio --> Tools
Nuget --> Engine
Tools --> Engine
Jna --> Header
Spm --> Header
Ffi --> Header
Koffi --> Header
Ctypes --> Header
Cgo --> Header
Hpp --> Header
Header --> Lib
Lib --> Abi
Abi --> Engine
Engine --> File| Layer | Project | Role |
|---|---|---|
| Engine | src/Nuventra.NuvexaDB | Pages, catalog, B+tree, NQL, encryption. Public .NET API. |
| C ABI | src/Nuventra.NuvexaDB.Native | Handle table + nuvexa_* exports. No storage of its own. |
| Tools / IDE | Tools, Explorer, Cli, VS / VS Code | Same engine, desktop UX. |
| Language SDKs | bindings/* | Marshal JSON and errors. Must not parse pages. |
.NET hosts skip the C ABI and reference the engine assembly. Everyone else links the AOT library that contains that assembly.
Why Native AOT (not a rewrite, not a sidecar)
| Approach | Why it was rejected or chosen |
|---|---|
| Port the engine per language | Format drift on HMAC, Argon2 params, index keys (s: / n: / b:). |
Wrap the nuvexa CLI | Process spawn, no in-process transactions, unusable on mobile. |
| Embed the full .NET runtime | Larger, slower start, harder to ship as an AAR / xcframework. |
| Native AOT shared library | One compile per RID. C symbols. In-process. Same fail-closed rules. |
PublishAot=true and NativeLib=Shared produce nuvexa.dylib / libnuvexa.so / nuvexa.dll. Android JNI uses the Bionic RID linux-bionic-arm64 (not android-arm64). iOS stays on net10.0 with PublishAotUsingRuntimePack=true and a shared dylib wrapped as Nuvexa.xcframework (ios-arm64 + iossimulator-arm64). Do not retarget the Native project to net10.0-ios (that restore path hits NETSDK1203).
The engine uses System.Text.Json. The native project sets JsonSerializerIsReflectionEnabledByDefault so NQL and index JSON keep working after trim. Do not turn that off.
ABI contract
Canonical header: nuvexa.h.
- In-process handle.
nuvexa_create/nuvexa_openreturn anintptr_t. The SDK does not hold a file descriptor of its own. - JSON in, JSON out. Insert / replace take a document object.
nuvexa_executetakes NQL and returns a JSON array. Index fields are a JSON string or array. - No exceptions across the boundary. Status:
0ok,1error,2encryption,3integrity,4not found. Message vianuvexa_last_error. Free everychar*withnuvexa_free. - Calling convention.
cdecl. UTF-8. Null key means unencrypted create / open (encrypted open then returnsNUVEXA_ENCRYPTION).
Host SDK C ABI Engine
--------- ----- ------
create(path, key) → nuvexa_create → NuvexaDatabase.Create
insert(col, json) → nuvexa_insert → GetCollection.InsertAsync
execute(nql) → nuvexa_execute → ExecuteAsync
close() → nuvexa_close → DisposeABI v2 adds catalog, count, stats, backup / compact / restore, rekey, transactions, and path-based GridFS. LINQ and fluent Find stay on the .NET API. Language SDKs must not grow a second query parser.
Platform map
| Host | Path to the engine | Binary it loads |
|---|---|---|
| .NET / MAUI | PackageReference / project reference | Managed IL (or the app’s own AOT) |
| Data Studio, CLI, VS, VS Code | ExplorerSession → engine | Same |
| Java / Kotlin desktop | JNA → nuvexa_* | nuvexa.dylib / .so / .dll |
| Android Kotlin / Java | Same Kotlin types + jniLibs | libnuvexa.so |
| Swift | nuvexa.h | desktop dylib, or Nuvexa.xcframework on iOS |
| Flutter | dart:ffi (DynamicLibrary.process() on iOS) | same library |
| React Native | iOS: ObjC++ → header; Android: Kotlin SDK; Node tests: koffi | same library |
| Python | ctypes → nuvexa_* | same library |
| Node.js | @nuventra/nuvexadb-node (koffi) | same library |
| Go | cgo → nuvexa.h | same library |
| C++ | nuvexa.hpp RAII | same library |
Two hosts must not open the same path at once (Explorer + app, or two JNI handles). That is an engine rule, not an SDK bug.
Publish and ship
src/Nuventra.NuvexaDB (engine)
│
▼
src/Nuventra.NuvexaDB.Native (exports)
│
▼
publish.sh -r <rid> (one native binary per RID)
│
├─ artifacts/native/osx-arm64/nuvexa.dylib
├─ …/win-x64/nuvexa.dll
├─ …/win-arm64/nuvexa.dll
├─ …/linux-x64/libnuvexa.so
├─ …/linux-arm64/libnuvexa.so
├─ …/android-arm64/libnuvexa.so → AAR jniLibs (published as linux-bionic-arm64)
└─ …/ios/Nuvexa.xcframework ← ios-arm64 + iossimulator-arm64 dylibsUnix consumers also look for libnuvexa.*. The publish script symlinks nuvexa.dylib → libnuvexa.dylib when Native AOT omits the lib prefix.
CI uploads desktop native artifacts, the Bionic Android .so, Nuvexa.xcframework, and language SDK packs. nuget.org / GitHub Packages push is commented out for now. Do not dotnet nuget push, npm publish, or dart pub publish from a local clone.
Adding a platform
- Do not fork
PageStore, WAL, orKeyDerivation. - Call nuvexa.h (or reuse an existing SDK that already does).
- Pass through NQL and document JSON unchanged.
- Map status
2/3to encryption / integrity errors. - Pass the interop fixture file.
- Document the loader env var and the RID you ship. Update this page and bindings.md.