How It Works
From Launch to Orbit
A step-by-step walkthrough of how two users set up a MindooDB tenant, exchange encrypted data, and collaborate — all without the server ever seeing plaintext.
Launch
Admin bootstraps the tenant
Create the Tenant
The admin creates a new tenant with a single call. This generates all keys, opens the tenant, and registers the first user in the directory.
const { tenant, adminUser, appUser, keyBag }
= await factory.createTenant({
tenantId: "acme",
adminName: "cn=admin/o=acme",
adminPassword: "••••••••",
userName: "cn=alice/o=acme",
userPassword: "••••••••",
}); Publish to Server
The admin registers the tenant on a MindooDB server. The server only stores encrypted data and public keys.
await tenant.publishToServer(
"https://sync.acme.com",
{ registerUsers: [appUser] }
); Create & Sync a Document
Alice creates a todo document and pushes the encrypted changes to the server.
const db = await tenant.openDB("todos");
const doc = await db.createDocument();
await db.changeDoc(doc, (d) => {
const data = d.getData();
data.title = "Buy groceries";
data.done = false;
});
const remote = await tenant.connectToServer(
"https://sync.acme.com", "todos"
);
await db.pushChangesTo(remote); Signal
A new user requests to join
Create Join Request
Bob creates his identity locally — his private keys never leave his device. He generates a join request containing only public keys.
const bob = await factory.createUserId(
"cn=bob/o=acme", "••••••••"
);
const joinRequestURI = factory.createJoinRequest(
bob, { format: "uri" }
);
// → mdb://join-request/eyJ2IjoxLCJ1c2Vy... Docking
Admin approves the join request
Approve Join Request
The admin approves Bob's request. This registers Bob in the directory and encrypts the symmetric keys with a shared password.
const joinResponseURI =
await tenant.approveJoinRequest(
joinRequestURI,
{
adminSigningKey: adminUser
.userSigningKeyPair.privateKey,
adminPassword: "••••••••",
sharePassword: "one-time-secret",
format: "uri",
}
);
// → mdb://join-response/eyJ2IjoxLCJ0ZW5h... Orbit
Real-time collaboration begins
Join, Pull, Modify, Push
Bob joins the tenant, pulls the latest data, marks the todo as done, and pushes back.
const { tenant: bobTenant } =
await factory.joinTenant(
joinResponseURI,
{
user: bob,
password: "••••••••",
sharePassword: "one-time-secret",
}
);
const remote = await bobTenant.connectToServer(
"https://sync.acme.com", "todos"
);
const db = await bobTenant.openDB("todos");
await db.pullChangesFrom(remote);
await db.syncStoreChanges();
const todo = await db.getDocument(todoDocId);
await db.changeDoc(todo, (d) => {
d.getData().done = true;
});
await db.pushChangesTo(remote); Fetch Changes
Alice pulls the latest changes and sees the todo is done.
await db.pullChangesFrom(remote);
await db.syncStoreChanges();
const updated = await db.getDocument(todoDocId);
console.log(updated.getData().done);
// → true ✓ Todo is done ✓ What the Server Sees vs. Doesn't See
Server sees
- Public signing keys (Ed25519)
- Public encryption keys (RSA-OAEP)
- Encrypted blobs (AES-256-GCM ciphertext)
- Entry metadata (timestamps, content hashes)
Server never sees
- Usernames (encrypted with admin RSA key)
- Document content (end-to-end encrypted)
- Private keys (stay on device)
- Shared passwords (out-of-band only)
- Symmetric encryption keys