strata_server/store
Persistent document store backed by shelf (ETS + DETS).
One actor owns the in-memory documents and deletion parent links.
A single store.dets checkpoint contains both. Shelf atomically replaces
that file on flush, so recovery sees a complete checkpoint, never one half
of a create/delete. Acceptance is in memory until a successful flush.
Parent links and tombstones
Section titled “Parent links and tombstones”create and delete manage a parent-child tree. Each child
document records its deletion parent id in the checkpoint.
On delete, the child and all descendants are removed and their
ids and former deletion parents added to the checkpoint's tombstones.
apply and create reject deleted ids. IDs cannot be reused; create a
new ID for a replacement. Tombstones have no expiry or garbage collection.
A successful checkpoint preserves deletion identity across restart.
delete is idempotent for a tombstoned child: it merges
parent_state and reports the child as removed again, without
touching the tombstone. A concurrent add-wins edit on the parent
can put a deleted child's reference back, and the repeat delete is
what removes it. A live child under a different parent is still
rejected with NotAChild.
ActorCallError
Section titled “ActorCallError”Errors returned when a synchronous actor call does not reply in time.
pub type ActorCallError { ActorCallTimedOut}ApplyError
Section titled “ApplyError”Rejections and ambiguous outcomes from normal delta application.
pub type ApplyError { ApplyDocumentLimit(SizeLimitExceeded) ApplyDocumentDeleted ApplyStateInvalid(json.DecodeError) ApplyMergeFailed(crdt.MergeError) ApplyStorageFailed(shelf.ShelfError) ApplyTimedOut}ApplyTimedOut
Section titled “ApplyTimedOut”The queued operation may still complete. This is not a rejection.
CreateError
Section titled “CreateError”Errors returned by create.
pub type CreateError { CreateDocumentLimit(SizeLimitExceeded) ChildExists CreateChildDeleted(child: String) CreateSelfParent(document_id: String) CreateParentDeleted(parent: String) CreateChildStateInvalid(child: String, reason: json.DecodeError) CreateParentStateInvalid(parent: String, reason: json.DecodeError) CreateMergeFailed(crdt.MergeError) CreateTimedOut}ChildExists
Section titled “ChildExists”A document with the given child id already exists.
CreateChildDeleted(child: String)
Section titled “CreateChildDeleted(child: String)”Deleted identities cannot be reused; allocate a new child id.
CreateSelfParent(document_id: String)
Section titled “CreateSelfParent(document_id: String)”create rejected a self-referential parent-child link.
CreateParentDeleted(parent: String)
Section titled “CreateParentDeleted(parent: String)”create rejected a parent document that was already deleted.
CreateChildStateInvalid(child: String, reason: json.DecodeError)
Section titled “CreateChildStateInvalid(child: String, reason: json.DecodeError)”The supplied child ORMap state could not be decoded.
CreateParentStateInvalid(parent: String, reason: json.DecodeError)
Section titled “CreateParentStateInvalid(parent: String, reason: json.DecodeError)”The supplied parent ORMap state could not be decoded.
CreateTimedOut
Section titled “CreateTimedOut”The queued create can still complete.
DeleteError
Section titled “DeleteError”Errors returned by delete.
pub type DeleteError { DeleteDocumentLimit(SizeLimitExceeded) NotAChild(child: String, parent: String) DeleteParentStateInvalid(parent: String, reason: json.DecodeError) DeleteMergeFailed(crdt.MergeError) DeleteTimedOut}NotAChild(child: String, parent: String)
Section titled “NotAChild(child: String, parent: String)”The live child has no recorded parent link, or its recorded parent is a different document, including after deletion.
DeleteParentStateInvalid(parent: String, reason: json.DecodeError)
Section titled “DeleteParentStateInvalid(parent: String, reason: json.DecodeError)”The supplied parent ORMap state could not be decoded.
DeleteTimedOut
Section titled “DeleteTimedOut”The queued delete can still complete.
DocumentSummary
Section titled “DocumentSummary”Read-only metadata for a known document.
pub type DocumentSummary { DocumentSummary(id: String, byte_size: Int, last_write_ms: Int, parent: option.Option(String))}SizeLimitExceeded
Section titled “SizeLimitExceeded”A merged document would exceed its allowed encoded size.
pub type SizeLimitExceeded { SizeLimitExceeded(document_id: String, bytes: Int, limit: Int)}StartError
Section titled “StartError”Errors start can return.
pub type StartError { ActorStartError(actor.StartError)}ActorStartError(actor.StartError)
Section titled “ActorStartError(actor.StartError)”The OTP actor failed to start (typically a downstream shelf.open
failure surfacing as the initialiser returning Error).
Opaque handle to the store actor.
pub type StoreConstants
Section titled “Constants”default_flush_interval_ms
Section titled “default_flush_interval_ms”Default flush interval (5 seconds) — see start.
pub const default_flush_interval_ms: Intdefault_max_document_bytes
Section titled “default_max_document_bytes”Default accepted encoded document size, including causal metadata (16 MiB).
pub const default_max_document_bytes: IntFunctions
Section titled “Functions”Merge a remote ORMap (encoded as a JSON string) into the store. Returns the accepted merged state, or an explicit error. Acceptance is in memory, not a crash-durable receipt. A timeout is ambiguous.
pub fn apply( Store, String, String) -> Result(json.Json, ApplyError)child_spec
Section titled “child_spec”Build a supervised store with a stable name across owner replacement.
Normal close shuts down a supervisor using AnySignificant; failures restart.
pub fn child_spec(String) -> #(Store, supervision.ChildSpecification(Nil))child_spec_with_limit
Section titled “child_spec_with_limit”Build the supervised store with an explicit encoded-document growth budget.
pub fn child_spec_with_limit( String, Int) -> #(Store, supervision.ChildSpecification(Nil))Flush pending writes and close the store. Call before shutdown to avoid losing unflushed data.
pub fn close(Store) -> Result(Nil, shelf.ShelfError)close_with_observations
Section titled “close_with_observations”Close and return the final operational snapshot, including close latency. Measurements are not persisted and otherwise end with the store owner.
pub fn close_with_observations(Store) -> Result(observations.Observations, shelf.ShelfError)create
Section titled “create”Create a child document under parent in one owner turn.
Records the parent link, stores child_state, and merges
parent_state into the parent document — all in one actor message.
Fails without side effects when the child already exists or the
parent is tombstoned.
A timeout returns CreateTimedOut: the queued operation can still complete.
pub fn create( Store, parent: String, child: String, child_state: String, parent_state: String) -> Result(Nil, CreateError)delete
Section titled “delete”Delete a child document and every descendant in one owner turn.
Verifies the parent link, merges parent_state, then removes the
child and every descendant by walking parent links. Returns all
removed document ids. Fails without side effects when the parent
link does not match a live child, or when parent_state does not
decode.
Repeating the call for an already deleted child succeeds: the merge still runs, so a reference re-added by a concurrent edit is removed, and the returned list names the child again. The child stays deleted and stays tombstoned.
A timeout returns DeleteTimedOut: the queued operation can still complete.
pub fn delete( Store, parent: String, child: String, parent_state: String) -> Result(List(String), DeleteError)document_summaries
Section titled “document_summaries”Get metadata for all known documents, sorted by id.
Returns Error(ActorCallTimedOut) if the store actor does not
reply before the configured timeout.
pub fn document_summaries(Store) -> Result(List(DocumentSummary), ActorCallError)Persist one complete document/parent checkpoint. On failure, memory is unchanged and a later flush may retry. A timeout leaves the outcome unknown.
pub fn flush(Store) -> Result(Nil, shelf.ShelfError)monitor
Section titled “monitor”Monitor this store owner for lifecycle coordination.
pub fn monitor(Store) -> process.Monitoroperational_data
Section titled “operational_data”Read bounded measurements. This shares the store mailbox and can time out during long work; reading metrics does not recursively instrument itself.
pub fn operational_data(Store) -> Result(observations.Observations, ActorCallError)snapshot
Section titled “snapshot”Get the current ORMap state for a document as a json.Json value
(ready to embed in a server frame).
Returns Ok(None) if no state has been recorded yet, or
Error(ActorCallTimedOut) if the store actor does not reply before
the configured timeout.
pub fn snapshot( Store, String) -> Result(option.Option(json.Json), ActorCallError)Start with the default flush interval (5 seconds).
pub fn start(String) -> Result(Store, StartError)start_with_interval
Section titled “start_with_interval”Start a store actor with a custom flush interval.
base_directory is a directory inside which the DETS file lives. It
is created if missing. Pass an interval of 0 to disable periodic
flushing (the actor still flushes on close).
pub fn start_with_interval( base_directory: String, flush_interval_ms: Int) -> Result(Store, StartError)start_with_limit
Section titled “start_with_limit”Start with a document growth budget. Zero disables it; negatives are invalid. Recovered documents above the budget remain readable and may shrink.
pub fn start_with_limit( String, Int, Int) -> Result(Store, StartError)