Embedded SDK
Dynoxide can be used as a Rust library, calling the database directly without any HTTP overhead. This is the fastest mode - single-digit microsecond operations, no serialisation, no network stack.
It also runs on platforms where DynamoDB Local can't, including iOS.
Add the dependency
[dependencies]
dynoxide-rs = { version = "0.12", default-features = false, features = ["native-sqlite"] }
For encrypted storage:
[dependencies]
dynoxide-rs = { version = "0.12", default-features = false, features = ["encryption"] }
Basic usage
use dynoxide::Database;
// In-memory database (great for tests)
let db = Database::memory().unwrap();
// Persistent, file-backed
let db = Database::new("data.db").unwrap();
// Encrypted (requires the encryption feature)
let db = Database::new_encrypted("data.db", "your-hex-key").unwrap();
The API uses DynamoDB-compatible request and response types as serde_json::Value. You call the same operations you'd call over HTTP - CreateTable, PutItem, Query, and so on - but without the network round trip.
Upgrading to 0.12
0.12.0 is source-breaking for library consumers, and only for them. The DynamoDB wire API and the CLI, server and MCP surfaces are unchanged, so if you run the binary rather than depend on the crate, nothing here affects you.
Two public types gained a field and became #[non_exhaustive]:
partiql::parser::Statement- theUpdateandDeletevariants carry areturningfield, which is what makes the new PartiQLRETURNINGsupport work.actions::batch_execute_statement::BatchStatementResponse- gained atable_namefield.
Code that constructs either type, or matches it exhaustively, needs a .. rest pattern. That's the whole migration.
Feature flags
| Flag | Default | Description |
|---|---|---|
native-sqlite |
Yes | Bundles plain SQLite. No OpenSSL. |
http-server |
Yes | The axum HTTP server exposing the DynamoDB JSON API. |
mcp-server |
Yes | The MCP server, over stdio and Streamable HTTP. |
import |
Yes | The dynoxide import CLI, with anonymisation. |
encryption |
No | SQLCipher with vendored OpenSSL. Adds Database::new_encrypted(). |
encryption-cc |
No | As encryption, but Apple CommonCrypto instead of OpenSSL. For macOS and iOS. |
encrypted-full |
No | encryption plus the server, MCP and import features. |
wasm-sqlite |
No | The wasm32 browser backend, a preview. Pulls in neither native SQLite nor the CLI. |
native-sqlite and encryption are mutually exclusive - they select different SQLite backends.
This one bites in workspaces. Cargo unifies features across a workspace, so if one crate depends on dynoxide-rs with default features (picking up native-sqlite) and another asks for encryption, both activate and the build fails with a backend conflict. Set default-features = false on every dynoxide-rs dependency in the workspace.
Testing
The embedded mode is particularly useful for integration tests. Each test can spin up its own Database::memory() instance - there's no shared state, no port conflicts, and startup is roughly 0.2ms.
#[test]
fn test_user_creation() {
let db = Database::memory().unwrap();
// create table, put item, assert...
}
No test fixtures, no cleanup, no waiting for a server to start. The database exists for the lifetime of the Database value and is dropped automatically.
For the full API surface and feature flags, see the crate documentation on crates.io.