Skip to content
Strata is pre-1.0. APIs, protocol, and storage format may change.

Documents and Deltas

Strata stores each document as a lattice ORMap. The server merges the state in each delta message and saves the merged map. It sends the submitted encoded state to the other applications that opened the document. Each receiving application must merge that state into its local document.

When an application opens a document, Strata sends a full snapshot. The v0 delta message also contains full encoded ORMap state. It does not contain only the changed operation.

strata_document provides optional types for ORMaps that contain LWW registers. A field has a key, JSON encoder, decoder, and document type tag. Gleam checks writes. Reads return Result because another application can send an incompatible value.

The server does not receive or enforce the schema. The schema does not change the v0 protocol or stored data format.

Each typed document owns a logical clock. A local write advances that clock, and merging remote state raises it to at least every observed register timestamp. Modern lattice registers use the writer's replica ID to resolve equal-clock concurrent writes deterministically.

import gleam/dynamic/decode
import gleam/json
import strata_document
import strata_document/schema
pub type Board
pub fn title() -> schema.Field(Board, String) {
schema.field("title", json.string, decode.string)
}
let document = strata_document.new("tab-1")
let assert Ok(document) =
strata_document.set(document, title(), "Sprint board")
let assert Ok(Some("Sprint board")) =
strata_document.get(document, title())

Document(tag) contains CRDT data. It does not contain UI code. strata_document targets Erlang and JavaScript, so it has no Lustre dependency.

An application can put the schema and its default view in companion modules:

strata_kanban/
├── doc_schema.gleam # fields and document tag
└── doc_view.gleam # default Lustre surface

The view accepts the typed document and a set of host actions:

pub fn surface(
cards: Document(CardDocument),
actions: Actions(msg),
) -> Element(msg)

The msg type belongs to the host. The same surface can run in the standalone kanban app or inside the workspace app. The host connects each action to its own update function.

Use a renderer parameter when one context needs another view. For example, a workspace pane can add a document id and connection data around the standard surface. A compact list can pass another renderer for the same Document(CardDocument).

The examples use this pattern in:

  • examples/kanban/src/strata_kanban/doc_view.gleam
  • examples/todo/src/strata_todo/doc_view.gleam
  • examples/workspace/src/strata_workspace/doc_view.gleam
  1. The application sends join for a document id.
  2. The server locates the document in its persistent store.
  3. The server replies with the current full snapshot.
  4. The application merges the snapshot into its local state and edits locally.
  5. The application sends encoded ORMap state in a delta message.
  6. The server merges and saves the accepted state.
  7. The server sends the submitted encoded state to the other applications.
  8. Each receiving application merges that state into its local document.
snapshotdelta
WhenOn every join and reconnectOn every edit
ContainsWhole documentEncoded ORMap state
Application actionMerge into local stateMerge into local state

The server sends a delta to the other sockets on the document. It does not send the delta back to the socket that submitted it.

An unknown document returns a snapshot with state: null. Joining an unknown document does not create stored state. The first valid delta stores the document.

Typed fields use LWW registers. Higher logical clocks win; equal clocks use the lexicographically greater replica ID. Removing a field uses ORMap observed-remove semantics, so a concurrent write wins over a removal.

This differs from Watershed's typed SharedMap: Watershed sends optimistic map operations through a central sequencer, which supplies a global order for conflicting sets and deletes. Strata clients resolve conflicts by merging CRDT state, but the server still uses one store owner on one node. This does not provide replication between Strata servers; see single-node deployment.

Snapshots use a single frame on every join and reconnect. The default 16 MiB encoded-document growth budget does not guarantee that a snapshot fits your proxy or client limits. Chunking and partial-transfer resume remain deferred in #5. Use the snapshot sizing guidance before increasing the budget.