Storage
This content is for the 0.2.0-alpha.3 version. Switch to the latest version for up-to-date documentation.
client.storage is a per-user key/value store. Values are encrypted at rest
by default with a key derived from the signed-in user’s identity, so the
accelerator only ever holds ciphertext.
Collections & ids
Section titled “Collections & ids”Data is organized as collection + id. A collection is just a namespace;
think of it like a table, and the id like a row key.
await client.storage.set("todos", "t1", { title: "Ship docs", done: false });
const todo = await client.storage.get<{ title: string; done: boolean }>("todos", "t1");// → { title: "Ship docs", done: false } (decrypted locally)
const existed = await client.storage.delete("todos", "t1"); // → true
const ids = await client.storage.list("todos"); // → ["t1", "t2", …] (ids only)get returns null for a missing key. list returns the ids in a
collection — not the values (see querying, below).
Encryption
Section titled “Encryption”By default set seals the value with AES-256-GCM before it leaves the device.
You can opt out per call to store plaintext — useful for data that needs to be
readable by something other than this user, or that you’ll encrypt yourself:
await client.storage.set("public-profile", "ada", { handle: "@ada" }, { encrypt: false });get transparently decrypts sealed values and passes plaintext through, so
reads don’t care which way it was written.
Realtime change feed
Section titled “Realtime change feed”Subscribe to changes to the signed-in user’s data — across all their devices:
const off = client.storage.on("change", (e) => { // e.collection, e.id, e.type ("set" | "delete"), e.data (decrypted | null) if (e.collection === "todos") refreshTodos();});
// lateroff();This rides the personal space’s own websocket (see Spaces), so a write on one device updates the others with no extra setup.
Querying
Section titled “Querying”There’s no server-side query. Because values are encrypted client-side, the accelerator can’t index or filter them — that’s the privacy trade-off. List the collection and filter after decryption:
const ids = await client.storage.list("todos");const todos = await Promise.all(ids.map((id) => client.storage.get("todos", id)));const open = todos.filter((t) => t && !t.done);See the To-do app example for a complete feature, and
the client.storage reference for signatures.
client.storage holds small JSON values. For files — anything from a few
KB to gigabytes — use FileStorage. It splits a file into fixed-size chunks
(4 MiB by default), encrypts each chunk with its own AES-256-GCM key, then
Reed–Solomon erasure-codes every chunk into data + parity shards. Shards are
stored content-addressed (named by the SHA-256 of their ciphertext), so a lost
shard can be reconstructed from the others and identical shards dedupe for free.
The output of a write is a manifest: the per-chunk keys, IVs, and shard hashes needed to reassemble the file. The shards are useless ciphertext without it — so the manifest is the thing you protect.
Two transports
Section titled “Two transports”ShardClient(always required) — talks to the open, content-addressed shard store (PUT/GET /api/shards/:hash). Anyone can put or get a shard, but without the manifest’s keys the bytes are noise.SharedSpaceClient(optional) — a ZK-gated, multi-user store that holds manifests behind a participant ACL. Only needed for the space-managed mode.
import { FileStorage, ShardClient } from "@muhkoo/connect";
const storage = new FileStorage({ shards: new ShardClient({ baseUrl: "https://api.muhkoo.dev" }), // chunkSize, dataShards, parityShards, concurrency all have sensible defaults});Mode A — you carry the manifest
Section titled “Mode A — you carry the manifest”Write to shards and get the manifest back; deliver it to readers yourself (for example, as an end-to-end-encrypted chat message). Nothing is stored server-side except the opaque shards.
const { manifest, stat } = await storage.writeFileToShards({ data: file, // Uint8Array | Blob | File metadata: { name: file.name, type: file.type },});
// …deliver `manifest` to the reader over an encrypted channel…
const { data } = await storage.readFileFromShards(manifest); // Uint8ArrayMode B — a gated space carries the manifest
Section titled “Mode B — a gated space carries the manifest”Pass a SharedSpaceClient and the manifest is stored in a multi-user space
behind its ACL; participants read, list, and delete by file id.
import { FileStorage, ShardClient, SharedSpaceClient } from "@muhkoo/connect";
const storage = new FileStorage({ shards: new ShardClient({ baseUrl }), space: new SharedSpaceClient({ baseUrl, /* commitment, secret, salt, circuits, … */ }),});
const stat = await storage.writeFile({ spaceId, data: file, metadata: { name, type } });const { data } = await storage.readFile(spaceId, stat.id);const files = await storage.listFiles(spaceId); // FileStat[]await storage.deleteFile(spaceId, stat.id);See the client.storage reference for full
signatures and the encrypted chat example for files in
practice.