pub trait NamespaceStore: MaybeSend + MaybeSync {
// Required methods
fn capabilities(&self) -> StoreCapabilities;
fn get(
&self,
p: &Partition,
key: &Key,
) -> impl Future<Output = Result<Option<Value>, StoreError>> + MaybeSend;
fn scan(
&self,
p: &Partition,
start: &Key,
end: &Key,
after: Option<&Cursor>,
limit: u32,
) -> impl Future<Output = Result<ScanPage, StoreError>> + MaybeSend;
fn apply(
&self,
p: &Partition,
batch: Batch,
) -> impl Future<Output = Result<BatchOutcome, StoreError>> + MaybeSend;
fn stats(
&self,
p: &Partition,
) -> impl Future<Output = Result<PartitionStats, StoreError>> + MaybeSend;
fn probe(&self) -> impl Future<Output = Result<(), StoreError>> + MaybeSend;
// Provided methods
fn has(
&self,
p: &Partition,
key: &Key,
) -> impl Future<Output = Result<bool, StoreError>> + MaybeSend { ... }
fn get_many(
&self,
p: &Partition,
keys: &[Key],
) -> impl Future<Output = Result<Vec<Option<Value>>, StoreError>> + MaybeSend { ... }
fn scan_many(
&self,
p: &Partition,
ranges: &[RangeScan],
) -> impl Future<Output = Result<Vec<ScanPage>, StoreError>> + MaybeSend { ... }
}Expand description
The metadata store: a partitioned, ordered key-value store. Any backend that can do an atomic conditional multi-key write per partition and an ordered range read can implement it: SQL is not required.
§Normative rules
- Declarative batch.
Self::applyis the only write. A backend never evaluates CAS, quota or replay logic: the pipeline plans every write and guards every value it read with a precondition. Size limits (Batch::validate) are checked first, and a violation writes nothing. - Reads are
get,has,get_many,scanandscan_many. No other query exists; every index is a key layout (store::keys). - Single writer is enough. Nothing may assume two
applycalls on one partition run concurrently, and nothing may hold a lock across an.awaitwaiting for anotherapply. The pipeline handles contention with an optimistic re-plan loop. - Cancellation safety. Dropping an
applyfuture at any.awaitleaves the partition fully before or fully after the batch, and the store usable: check-and-write runs in one non-yielding step (a synchronous transaction that runs to completion even if the future is dropped, a Durable ObjecttransactionSync, a mutex held without awaits). The dropped batch may still commit later (a blocking task keeps running): every later observation is exactly the state before or after it, never torn, and once the after state has been observed the before state never reappears. A poisoned lock is recovered, never propagated. - Durability.
Committedmeans durable to the level the backend documents. - Atomicity scope. One batch is one partition; nothing needs atomicity across partitions. Cross-partition effects are ordered by the pipeline or carried by outbox rows a core-owned relay delivers at least once, idempotently. A backend needs nothing beyond this trait and a way to run timers.
- Bounded growth. The core deletes what it no longer needs through
ordinary batches; a backend reclaims deleted keys and reports
Self::stats. At its cap it returnsStoreError::Fullfor batches that add data and keeps serving reads and deletes. A batch whose writes are all deletes (preconditions allowed) never returnsFull, so onFullthe caller retries pruning as a delete-only batch. - Commit deadline.
Precondition::NotAfteris evaluated against the backend’s own clock (theSQLitehost’s, the Durable Object’s, an injected one in memory), read once inside the same non-yielding check-and-write step as the key checks, never the caller’s clock; an invalid reading fails closed. A late batch therefore cannot commit after its deadline, whenever it arrives. A miss isBatchOutcome::DeadlinePassed: the pipeline answers a retryableunavailable, neveraborted,deadline_exceededorresource_exhausted, and may re-plan the write once within the envelope’s validity (SPEC-WRITE-GRANTS §5.5). Callers set the deadline with a margin that covers the skew between their clock and the backend’s plus the longest synchronous span between reading the clock and committing: on WorkersDate.now()does not advance during synchronous execution, so the backend’s reading can lag real time by that span.
Required Methods§
Sourcefn capabilities(&self) -> StoreCapabilities
fn capabilities(&self) -> StoreCapabilities
What this store supports.
Sourcefn get(
&self,
p: &Partition,
key: &Key,
) -> impl Future<Output = Result<Option<Value>, StoreError>> + MaybeSend
fn get( &self, p: &Partition, key: &Key, ) -> impl Future<Output = Result<Option<Value>, StoreError>> + MaybeSend
The value at key, if any.
Sourcefn scan(
&self,
p: &Partition,
start: &Key,
end: &Key,
after: Option<&Cursor>,
limit: u32,
) -> impl Future<Output = Result<ScanPage, StoreError>> + MaybeSend
fn scan( &self, p: &Partition, start: &Key, end: &Key, after: Option<&Cursor>, limit: u32, ) -> impl Future<Output = Result<ScanPage, StoreError>> + MaybeSend
Up to limit (at least 1) entries in [start, end), ascending by
key bytes. after resumes strictly after the cursor’s position; a
cursor outside [start, end) (forged, or from another range) is
StoreError::Invalid. A page may hold fewer than limit entries
and still return next: callers page until next is None.
Sourcefn apply(
&self,
p: &Partition,
batch: Batch,
) -> impl Future<Output = Result<BatchOutcome, StoreError>> + MaybeSend
fn apply( &self, p: &Partition, batch: Batch, ) -> impl Future<Output = Result<BatchOutcome, StoreError>> + MaybeSend
One atomic, all-or-nothing batch: validate it, read the backend
clock once, check every precondition in order against committed
state (NotAfter against that reading); on the first failure return
BatchOutcome::PreconditionFailed or
BatchOutcome::DeadlinePassed and write nothing, otherwise apply
every write and return BatchOutcome::Committed.
Sourcefn stats(
&self,
p: &Partition,
) -> impl Future<Output = Result<PartitionStats, StoreError>> + MaybeSend
fn stats( &self, p: &Partition, ) -> impl Future<Output = Result<PartitionStats, StoreError>> + MaybeSend
Storage used by one partition; may be approximate or up to 60 s stale.
Provided Methods§
Sourcefn has(
&self,
p: &Partition,
key: &Key,
) -> impl Future<Output = Result<bool, StoreError>> + MaybeSend
fn has( &self, p: &Partition, key: &Key, ) -> impl Future<Output = Result<bool, StoreError>> + MaybeSend
Whether key holds a value.
Sourcefn get_many(
&self,
p: &Partition,
keys: &[Key],
) -> impl Future<Output = Result<Vec<Option<Value>>, StoreError>> + MaybeSend
fn get_many( &self, p: &Partition, keys: &[Key], ) -> impl Future<Output = Result<Vec<Option<Value>>, StoreError>> + MaybeSend
Several keys in one round trip; results in input order. The default
issues sequential Self::get calls.
Sourcefn scan_many(
&self,
p: &Partition,
ranges: &[RangeScan],
) -> impl Future<Output = Result<Vec<ScanPage>, StoreError>> + MaybeSend
fn scan_many( &self, p: &Partition, ranges: &[RangeScan], ) -> impl Future<Output = Result<Vec<ScanPage>, StoreError>> + MaybeSend
Scan a served prefix of ranges in order. A nonempty request returns
at least its first page and at most one page per range; callers
re-request any unserved suffix. Every returned page obeys Self::scan.
The default serves every range sequentially.
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".