pub struct Database { /* private fields */ }Expand description
Represents a collection of related entries, like a traditional database or a branch in a version control system.
Each Database is identified by the ID of its root Entry and manages the history of data
associated with that root. It interacts with the underlying storage through the Instance handle.
Implementations§
Source§impl Database
impl Database
Sourcepub async fn create(
instance: &Instance,
signing_key: PrivateKey,
initial_settings: Doc,
) -> Result<Self>
pub async fn create( instance: &Instance, signing_key: PrivateKey, initial_settings: Doc, ) -> Result<Self>
Creates a new Database instance with a user-provided signing key.
This constructor creates a new database using a signing key that’s already in memory (e.g., from UserKeyManager), without requiring the key to be stored in the backend. This is the preferred method for creating databases in a User context where keys are managed separately from the backend.
The created database will use a DatabaseKey for all subsequent operations,
meaning transactions will use the provided key directly rather than looking it up
from backend storage.
§Auth Bootstrapping
Auth is always bootstrapped with the signing key as Admin(0). Passing auth
configuration in initial_settings is an error — additional keys must be added
via follow-up transactions after creation.
§Arguments
instance- Instance handle for storage and coordinationsigning_key- The signing key to use for the initial commit and subsequent operations. This key should already be decrypted and ready to use. The public key is derived automatically and used as the key identifier in auth settings.initial_settings-DocCRDT containing the initial settings for the database. UseDoc::new()for an empty settings document.
§Returns
A Result containing the new Database instance configured with a DatabaseKey.
§Example
let instance = Instance::open_backend(Box::new(InMemory::new())).await?;
let (signing_key, _public_key) = generate_keypair();
let mut settings = Doc::new();
settings.set("name", "my_database");
// Create database with user-managed key (no backend storage needed)
let database = Database::create(&instance, signing_key, settings).await?;
// All transactions automatically use the provided key
let tx = database.new_transaction().await?;Sourcepub async fn create_with_init<F>(
instance: &Instance,
signing_key: PrivateKey,
initial_settings: Doc,
init: F,
) -> Result<Self>
pub async fn create_with_init<F>( instance: &Instance, signing_key: PrivateKey, initial_settings: Doc, init: F, ) -> Result<Self>
Creates a new database with an initialization callback that runs inside the genesis transaction.
This is the underlying constructor used by Self::create and by
User::new_database() (the builder API). The callback receives the
genesis transaction after _settings and _root have been staged but
before commit, allowing additional subtrees — stores, initial doc data,
records — to be written into the same entry that establishes the
database root.
All writes performed in the callback become part of the single genesis entry: one signed entry, one backend write, atomic. If the callback returns an error, the transaction is dropped without committing and no database is created.
§Arguments
instance- Instance handle for storage and coordinationsigning_key- The private key for this database (becomes Admin(0))initial_settings- Initial settings document (must not contain auth)init- Callback run against the genesis transaction before commit
Sourcepub async fn open(instance: &Instance, root_id: &ID) -> Result<Self>
pub async fn open(instance: &Instance, root_id: &ID) -> Result<Self>
Opens an existing database by its root ID.
Verifies the root entry exists in the backend, then returns a handle
for read-only access. To perform authenticated writes, chain
.with_key(key) after opening.
§Arguments
instance- Instance handle for storage and coordinationroot_id- The root entry ID of the database to open
§Errors
Returns an error if the root entry does not exist in the backend.
§Example
// Open database for reading
let db = Database::open(&instance, &root_id).await?;
// Open database with a signing key for writes
let db = Database::open(&instance, &root_id).await?.with_key(signing_key);
let tx = db.new_transaction().await?;Sourcepub async fn open_remote(
instance: &Instance,
conn: RemoteConnection,
root_id: &ID,
identity: SigKey,
) -> Result<Self>
pub async fn open_remote( instance: &Instance, conn: RemoteConnection, root_id: &ID, identity: SigKey, ) -> Result<Self>
Open a database for remote access through a service connection.
Constructs a Database handle whose backing
Backend is a
RemoteBackend bound to
identity, so every Transaction/Store read and write travels
over the connection as a DatabaseOp under that identity. The
identity must match the database’s auth settings for the caller’s
key. Instance::connect must be used to create the instance.
Sourcepub fn with_key(self, key: impl Into<DatabaseKey>) -> Self
pub fn with_key(self, key: impl Into<DatabaseKey>) -> Self
Attach a signing key to this database handle.
The key is stored for use by future transactions. No validation is
performed; invalid keys will cause errors at commit time or when
calling current_permission.
Calling with_key again replaces any previously-attached key — the
most recent call wins.
To discover which SigKey identity to use for a given public key,
use Database::find_sigkeys.
Sourcepub fn allow_unverified(self) -> Self
pub fn allow_unverified(self) -> Self
Include Unverified entries in this handle’s reads.
By default a Database exposes only the Verified frontier: the
maximal prefix of the DAG (an ancestor-closed set, starting at the
root) in which every entry is Verified. Tips that are still
Unverified — and everything reachable only through them — are hidden,
so a default read never reflects state this node could not authenticate.
Calling allow_unverified opts this handle into the looser view that
also includes Unverified entries (everything except Failed, which
is always dropped). Use it when you explicitly want to observe
not-yet-verified data — e.g. freshly synced entries whose pinned
_settings this node does not hold yet.
This is a per-handle setting and composes with with_key:
let db = Database::open(&instance, &root_id)
.await?
.with_key(signing_key)
.allow_unverified();§Note on CRDT coherence
The Verified frontier is a prefix cut, not a per-value filter. An
interior Unverified entry hides all of its descendants from the
default view even if those descendants are themselves Verified,
because exposing them without their unverifiable ancestor would yield
an incoherent CRDT state. Run verify to promote the
blocking entry, or allow_unverified to read past it.
Sourcepub async fn find_sigkeys(
instance: &Instance,
root_id: &ID,
pubkey: &PublicKey,
) -> Result<Vec<(SigKey, Permission)>>
pub async fn find_sigkeys( instance: &Instance, root_id: &ID, pubkey: &PublicKey, ) -> Result<Vec<(SigKey, Permission)>>
Find all SigKeys that a public key can use to access a database.
This static helper method loads a database’s authentication settings and returns all possible SigKeys that can be used with the given public key. This is useful for discovering authentication options before opening a database.
Returns all matching SigKeys including:
- Specific key names where the pubkey matches
- Global permission if available
- Single-hop delegation paths (pubkey found in a directly delegated tree)
The results are sorted by permission level, highest first, making it easy to select the most privileged access available.
§Arguments
instance- Instance handle for storage and coordinationroot_id- Root entry ID of the database to checkpubkey- Public key string (e.g., “Ed25519:abc123…”) to look up
§Returns
A vector of (SigKey, Permission) tuples, sorted by permission (highest first). Returns empty vector if no valid access methods are found.
§Errors
Returns an error if:
- Database cannot be loaded
- Auth settings cannot be parsed
§Example
// Find all SigKeys this pubkey can use (sorted highest permission first)
let sigkeys = Database::find_sigkeys(&instance, &root_id, &pubkey).await?;
// Use the first available SigKey (highest permission)
if let Some((sigkey, _permission)) = sigkeys.first() {
let key = DatabaseKey::with_identity(signing_key, sigkey.clone());
let database = Database::open(&instance, &root_id).await?.with_key(key);
}Sourcepub fn auth_identity(&self) -> Option<&SigKey>
pub fn auth_identity(&self) -> Option<&SigKey>
Get the auth identity for this database’s configured key.
Sourcepub fn on_write<F, Fut>(&self, callback: F) -> Result<WriteCallback>
pub fn on_write<F, Fut>(&self, callback: F) -> Result<WriteCallback>
Register a callback to be invoked when entries are written to this database.
The callback fires for both local writes (transaction commits) and remote
writes (sync). Branch on WriteEvent::source inside
the closure if you only care about one.
Returns a WriteCallback handle. Drop it to unregister. Call
WriteCallback::detach to leave the callback registered for the life
of the Instance without holding the handle.
Important: Callbacks are registered at the Instance level and fire for all
writes to the database tree (identified by root ID), regardless of which
Database handle performed the write or registered the callback.
§⚠️ Limited semantics on a connected (remote) Instance
On a daemon-backed Instance, on_write is best-effort and partial.
It only observes writes whose commit ran through this client’s
Instance::put_entry. The following writes are not observed:
- Commits made by other client processes connected to the same daemon.
- Entries the daemon receives via sync from peers.
- Anything the daemon writes outside this client’s commit path.
In addition, WriteEvent::previous_tips is always empty for writes
that go through a remote backend — the canonical DAG lives on the daemon
and the client has nothing local to read tips from. Callbacks that diff
previous_tips against event.entries() will see “the world was empty”
on every event.
If you need full cross-client / cross-sync notification semantics, use a
local Instance for now. A server-push subscription path is planned —
when it lands, this method will gain the same contract on remote.
§Callback contract (local Instance)
- Local writes: fires once per transaction commit; the
WriteEventcontains exactly one entry. - Remote writes: fires once per sync batch (not per entry); the
WriteEventmay contain multiple entries received together. - All entries in the event are fully persisted before the callback fires.
WriteEvent::previous_tipscontains the DAG tips from before the write, so consumers can determine exactly what changed.- Errors are logged but do not prevent other callbacks from running.
- The
dbargument is a read-onlyDatabasehandle (noDatabaseKeyconfigured): you can read settings, entries, and metadata, but cannot commit transactions through it. To write from inside a callback, resolve throughdb.instance()?and open the database with the appropriate key. - Reentrance: writes are serialized per-tree via an async lock that is held while callbacks run. A callback must not commit a transaction on the same tree it was invoked for — that would deadlock. Spawn a task or write to a different tree instead.
§Example
let instance = Instance::open_backend(Box::new(InMemory::new())).await?;
let cb = database.on_write(|event, db| {
let count = event.entries().len();
let source = event.source();
let db_id = db.root_id().clone();
async move {
println!("{count} entries written to {db_id} ({source:?})");
Ok(())
}
})?;
// Drop `cb` to unregister, or:
cb.detach(); // keep registered for the life of the InstanceIf a callback needs the Instance, call Database::instance on
the db argument.
Sourcepub fn instance(&self) -> Result<Instance>
pub fn instance(&self) -> Result<Instance>
Upgrade the weak instance reference to a strong reference.
Database holds a WeakInstance, so this can
fail if the owning Instance has already been dropped.
Sourcepub async fn get_settings(&self) -> Result<SettingsStore>
pub async fn get_settings(&self) -> Result<SettingsStore>
Get a read-only settings store for the database.
Returns a SettingsStore that provides access to the database’s settings. Since this creates an internal transaction that is never committed, any modifications made through the returned store will not persist.
For making persistent changes to settings, create a transaction and use
Transaction::get_settings() instead.
§Returns
A Result containing the SettingsStore for settings or an error.
§Example
// Read-only access
let settings = database.get_settings().await?;
let name = settings.get_name().await?;
// For modifications, use a transaction:
let txn = database.new_transaction().await?;
let settings = txn.get_settings()?;
settings.set_name("new_name").await?;
txn.commit().await?;Sourcepub async fn get_name(&self) -> Result<String>
pub async fn get_name(&self) -> Result<String>
Get the name of the database from its settings store
Sourcepub async fn new_transaction(&self) -> Result<Transaction>
pub async fn new_transaction(&self) -> Result<Transaction>
Create a new atomic transaction on this database
This creates a new atomic transaction containing a new Entry. The atomic transaction will be initialized with the current state of the database. If a default authentication key is set, the transaction will use it for signing.
§Returns
A Result<Transaction> containing the new atomic transaction
Sourcepub async fn new_transaction_at(
&self,
snapshot: &Snapshot,
) -> Result<Transaction>
pub async fn new_transaction_at( &self, snapshot: &Snapshot, ) -> Result<Transaction>
Create a new atomic transaction on this database anchored at a specific snapshot.
The transaction’s parents are taken from the provided snapshot’s tips instead of the database’s current state. This allows creating complex DAG structures like diamond patterns for testing and advanced use cases.
§Arguments
snapshot- The snapshot to anchor the transaction at
§Returns
A Result<Transaction> containing the new atomic transaction
Sourcepub async fn transaction_context(
&self,
stores: &[String],
scope: ReadScope,
) -> Result<TransactionContext>
pub async fn transaction_context( &self, stores: &[String], scope: ReadScope, ) -> Result<TransactionContext>
Gather everything a client needs to build and sign a transaction
locally for the given stores, with parents drawn from scope’s
projection.
This is single-sourced: both the server’s BeginTransaction
handler and the Phase-3 remote seam call it, so
Transaction::commit’s build-sign path has one source of truth for
context gathering.
scope=AllowUnverified opens against the raw DAG (only Failed
dropped); the default Verified scope uses the Verified frontier.
The returned [TransactionContext] carries everything needed for
one round-trip transaction build: main parents + heights, per-store
subtree parents + heights, settings tips, and the merged _settings
CRDT state this entry is authored against.
Sourcepub async fn get_store_state(&self, store: &str) -> Result<Value>
pub async fn get_store_state(&self, store: &str) -> Result<Value>
Server-materialized merged state of an unencrypted store, as a
serde_json::Value against the database’s Verified frontier.
Creates an ephemeral transaction, deserializes every entry’s
store data as Doc, and merges them via Doc’s LWW merge —
the same merge Store<T> would perform client-side. All current
store types (DocStore, Table, Settings) serialize their data as
JSON, so Doc-typed deserialization works universally.
§Encrypted stores
Encrypted stores cannot be materialized this way (the ephemeral
transaction has no encryptor, so serde_json::from_slice::<Doc>
would fail on ciphertext). The caller must use
get_store_entries for encrypted
stores and decrypt+merge client-side.
Sourcepub async fn get_store_entries(
&self,
store: &str,
tips: &[ID],
_scope: ReadScope,
) -> Result<Vec<Entry>>
pub async fn get_store_entries( &self, store: &str, tips: &[ID], _scope: ReadScope, ) -> Result<Vec<Entry>>
Ordered (by subtree height), verifiable, opaque store entries
reachable from tips within scope.
This is the universal primitive — works for encrypted and
unencrypted stores alike because it returns raw Entry records
with opaque RawData; no deserialization
or merge runs server-side. The per-subtree-height ordering
(ascending, then by ID for tiebreaking) is exactly the canonical
CRDT replay order produced by
sort_entries_by_subtree_height.
When scope is [ReadScope::Verified] and tips are the
Verified-frontier tips from snapshot,
every returned entry is guaranteed Verified (the frontier is
ancestor-closed). For [ReadScope::AllowUnverified], entries
reachable from unverified tips are included.
Sourcepub async fn with_transaction<F, Fut, R>(&self, f: F) -> Result<R>
pub async fn with_transaction<F, Fut, R>(&self, f: F) -> Result<R>
Execute a closure within a transaction and commit the result.
This is a convenience wrapper for the common pattern of creating a transaction,
performing store operations, and committing. The transaction is committed after
the closure returns Ok. If the closure returns Err, the transaction is
dropped without committing.
For read-only access, use get_store_viewer instead.
§Arguments
f- A closure that receives theTransactionand performs store operations. The closure should returnOk(R)on success.
§Returns
On success, returns the value produced by the closure after committing.
The commit ID is not returned; use new_transaction
directly if you need it.
§Errors
Returns an error if transaction creation, the closure, or commit fails. If the closure fails, the transaction is not committed.
§Example
// Insert a record and get its generated key
let key = db.with_transaction(|txn| async move {
let store = txn.get_store::<Table<Todo>>("todos").await?;
store.insert(Todo { title: "Buy milk".into() }).await
}).await?;
// Multiple operations in one atomic transaction
db.with_transaction(|txn| async move {
let store = txn.get_store::<Table<Todo>>("todos").await?;
store.insert(Todo { title: "First".into() }).await?;
store.insert(Todo { title: "Second".into() }).await?;
Ok(())
}).await?;Sourcepub async fn insert_raw(&self, entry: Entry) -> Result<ID>
pub async fn insert_raw(&self, entry: Entry) -> Result<ID>
Insert an entry into the database without modifying or validating it. Primarily for testing / full control over raw entry storage.
The entry is stored Unverified: this path runs no validation, so it
cannot honestly claim the entry is verified, and the storage API no
longer accepts a caller-asserted status. Only the local validation
pass promotes entries to Verified.
Sourcepub async fn get_store_viewer<T>(&self, name: impl Into<String>) -> Result<T>where
T: Store,
pub async fn get_store_viewer<T>(&self, name: impl Into<String>) -> Result<T>where
T: Store,
Get a Store type that will handle accesses to the Store This will return a Store initialized to point at the current state of the database.
The returned store should NOT be used to modify the database, as it intentionally does not expose the Transaction. Since the Transaction is never committed, it does not have any effect on the database.
Sourcepub async fn snapshot(&self) -> Result<Snapshot>
pub async fn snapshot(&self) -> Result<Snapshot>
Get the current tips (leaf entries) of the main database branch.
Tips represent the latest entries in the database’s main history, forming the heads of the DAG.
If any raw tip is Unverified, an opportunistic Self::verify pass
runs first (entries arrive Unverified from sync; this promotes the
ones whose pinned _settings are now held).
The returned tips then depend on the handle’s view:
- default — the Verified frontier: the tips of the maximal
ancestor-closed, all-
Verifiedprefix of the DAG. A still-Unverifiedtip is replaced by its nearestVerifiedancestors; anything reachable only through anUnverifiedentry is excluded. allow_unverified— the raw tips with onlyFailedentries dropped (Unverifiedtips are kept).
Failed entries are dropped in both cases. While a verify
pass is on the stack the frontier is bypassed (its own reads must see the
raw DAG to reconstruct pinned _settings); a remote backend returns its
raw tips unchanged (the server owns verification).
§Returns
A Result containing the Snapshot of tip entries or an error.
Sourcepub async fn get_tip_entries(&self) -> Result<Vec<Entry>>
pub async fn get_tip_entries(&self) -> Result<Vec<Entry>>
Get the full Entry objects for the current tips of the main database branch.
§Returns
A Result containing a vector of the tip Entry objects or an error.
Sourcepub async fn get_entry<I: Into<ID>>(&self, entry_id: I) -> Result<Entry>
pub async fn get_entry<I: Into<ID>>(&self, entry_id: I) -> Result<Entry>
Get a single entry by ID from this database.
This is the primary method for retrieving entries after commit operations. It provides safe, high-level access to entry data without exposing backend details.
The method verifies that the entry belongs to this database by checking its root ID. If the entry exists but belongs to a different database, an error is returned.
§Arguments
entry_id- The ID of the entry to retrieve (accepts anything that converts to ID/String)
§Returns
A Result containing the Entry or an error if not found or not part of this database
§Example
let entry_id = txn.commit().await?;
let entry = tree.get_entry(&entry_id).await?; // Using &ID
let entry = tree.get_entry(entry_id.clone()).await?; // Using ID
println!("Entry signature: {:?}", entry.sig);Sourcepub async fn get_entries<I, T>(&self, entry_ids: I) -> Result<Vec<Entry>>
pub async fn get_entries<I, T>(&self, entry_ids: I) -> Result<Vec<Entry>>
Get multiple entries by ID efficiently.
This method retrieves multiple entries more efficiently than multiple get_entry() calls
by minimizing conversion overhead and pre-allocating the result vector.
The method verifies that all entries belong to this database by checking their root IDs. If any entry exists but belongs to a different database, an error is returned.
§Parameters
entry_ids- An iterable of entry IDs to retrieve
§Returns
A Result containing a vector of Entry objects or an error if any entry is not found or not part of this database
§Example
let entry_ids = vec![ID::from_bytes("id1"), ID::from_bytes("id2")];
let entries = tree.get_entries(entry_ids).await?;Sourcepub async fn verify_entry_signature<I: Into<ID>>(
&self,
entry_id: I,
) -> Result<bool>
pub async fn verify_entry_signature<I: Into<ID>>( &self, entry_id: I, ) -> Result<bool>
Verify an entry’s signature and authentication against the database’s configuration that was valid at the time of entry creation.
This method validates that:
- The entry belongs to this database
- The entry is properly signed with a key that was authorized in the database’s authentication settings at the time the entry was created
- The signature is cryptographically valid
The method uses the entry’s metadata to determine which authentication settings were active when the entry was signed, ensuring that entries remain valid even if keys are later revoked or settings change.
§Arguments
entry_id- The ID of the entry to verify (accepts anything that converts to ID/String)
§Returns
A Result containing true if the entry is valid and properly authenticated, false if authentication fails
§Errors
Returns an error if:
- The entry is not found
- The entry does not belong to this database
- The entry’s metadata cannot be parsed
- The historical authentication settings cannot be retrieved
Sourcepub async fn current_permission(&self) -> Result<Permission>
pub async fn current_permission(&self) -> Result<Permission>
Get the permission level for this database’s configured signing key.
Returns the effective permission for the key that was configured when opening
or creating this database. This uses the already-resolved identity stored in
the database’s DatabaseKey.
§Returns
The effective Permission for the configured signing key.
§Errors
Returns an error if:
- No signing key is configured (database opened without authentication)
- The database settings cannot be retrieved
- The key is no longer valid in the current auth settings
§Example
// Check if the current key has Admin permission
let permission = database.current_permission().await?;
if permission.can_admin() {
println!("Current key has Admin permission!");
}Sourcepub async fn verify(&self) -> Result<VerifyReport>
pub async fn verify(&self) -> Result<VerifyReport>
Attempt to verify every Unverified entry in this database.
For each Unverified entry, reconstruct the _settings it pins
(see Self::get_historical_settings_for_entry) and validate its
signature + permissions against that:
- an ancestor is
Failed→ this entry isFailedtoo (quarantine propagates down the branch); - an ancestor is still
Unverified, or not held locally yet (partial sync) → leftUnverified(retried once the ancestor verifies / the missing entry arrives); - pinned
_settingsnot fully held locally → leftUnverified(a later pass retries once the set syncs in); - signature + permissions valid → promoted to
Verified; - definitively invalid → marked
Failed(dropped from reads).
Verification is prefix-closed: an entry is Verified only if its
entire ancestor history is Verified. It is therefore impossible for a
tip to be Verified while one of its ancestors is not, which is what
makes the Verified set ancestor-closed (see Self::allow_unverified).
Already-Verified entries are never demoted here; that is a separate,
not-yet-built path. Local-only — verification is a per-node decision
and is never delegated to a peer.
Sourcepub async fn get_all_entries(&self) -> Result<Vec<Entry>>
pub async fn get_all_entries(&self) -> Result<Vec<Entry>>
Get all entries in this database.
⚠️ Warning: This method loads all entries into memory. Use with caution on large databases.
Consider using snapshot() or get_tip_entries() for more efficient access patterns.
§Returns
A Result containing a vector of all Entry objects in the database
Trait Implementations§
Auto Trait Implementations§
impl Freeze for Database
impl !RefUnwindSafe for Database
impl Send for Database
impl Sync for Database
impl Unpin for Database
impl UnsafeUnpin for Database
impl !UnwindSafe for Database
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<T> CompatExt for T
impl<T> CompatExt for T
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more