Spaces
This content is for the 0.2.0-alpha.3 version. Switch to the latest version for up-to-date documentation.
Storage and messaging look like separate features, but they’re two views of one primitive: a space. A space is an addressable unit of state on the edge that holds data and a realtime websocket.
There are two kinds.
Personal space
Section titled “Personal space”One per user, addressed by their commitment. It’s a private key/value store —
this is what client.storage reads and writes. Its websocket carries a
change feed: when you write from one device, your other devices get a
change event. So client.storage.on('change') isn’t a bolted-on extra — it’s
the space’s own sync channel.
await client.storage.set("notes", "n1", { body: "…" }); // writeclient.storage.on("change", (e) => …); // same space, live feedShared space
Section titled “Shared space”A multi-party space — the SDK calls it a Space. It holds space-scoped file
shards, a persisted
message log, and a websocket that fans out frames to everyone present. Two
client surfaces ride it:
client.message— lightweight realtime:- pub/sub —
publish/subscriberide plaintext relay frames; - direct messages —
send('user:x', …)is end-to-end encrypted via the Double Ratchet, delivered in the recipient’s inbox space. Forward-secret and ephemeral: no history, and both parties must be online.
- pub/sub —
client.space— fan-out group channels with history (what group chat is built on). Each space has one symmetric group key; a message is sealed once and is decryptable by every member, so the server can persist it and replay it as history. Channels are named via an app-scoped registry —createChannel/joinChannel/listChannels. See Messaging and theclient.spacereference.
How members get the group key (server-blind)
Section titled “How members get the group key (server-blind)”The relay never sees the group key. A new member’s copy is delivered by ECIES-wrapping it to their identity public key, so the server only ever stores opaque blobs. To keep a channel joinable even when no human member is online, each app runs a keeper: a trusted, always-available member that holds the group key and re-issues it to newcomers on join. The keeper can read its channels; the relay + storage stay blind.
Why this matters
Section titled “Why this matters”Two practical takeaways fall out of the model:
- Realtime is free. Anything you store or send already has a space behind it, so live updates don’t need extra infrastructure.
- Encrypted-at-rest ≠ queryable. Personal-space values are sealed
client-side, so the server can’t index or query them.
storage.list()enumerates ids; filtering happens on the client after decryption. (See Storage.)