Skip to main content

NamespaceStore

Trait NamespaceStore 

Source
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

  1. Declarative batch. Self::apply is 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.
  2. Reads are get, has, get_many, scan and scan_many. No other query exists; every index is a key layout (store::keys).
  3. Single writer is enough. Nothing may assume two apply calls on one partition run concurrently, and nothing may hold a lock across an .await waiting for another apply. The pipeline handles contention with an optimistic re-plan loop.
  4. Cancellation safety. Dropping an apply future at any .await leaves 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 Object transactionSync, 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.
  5. Durability. Committed means durable to the level the backend documents.
  6. 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.
  7. 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 returns StoreError::Full for batches that add data and keeps serving reads and deletes. A batch whose writes are all deletes (preconditions allowed) never returns Full, so on Full the caller retries pruning as a delete-only batch.
  8. Commit deadline. Precondition::NotAfter is evaluated against the backend’s own clock (the SQLite host’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 is BatchOutcome::DeadlinePassed: the pipeline answers a retryable unavailable, never aborted, deadline_exceeded or resource_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 Workers Date.now() does not advance during synchronous execution, so the backend’s reading can lag real time by that span.

Required Methods§

Source

fn capabilities(&self) -> StoreCapabilities

What this store supports.

Source

fn get( &self, p: &Partition, key: &Key, ) -> impl Future<Output = Result<Option<Value>, StoreError>> + MaybeSend

The value at key, if any.

Source

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.

Source

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.

Source

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.

Source

fn probe(&self) -> impl Future<Output = Result<(), StoreError>> + MaybeSend

A cheap health check.

Provided Methods§

Source

fn has( &self, p: &Partition, key: &Key, ) -> impl Future<Output = Result<bool, StoreError>> + MaybeSend

Whether key holds a value.

Source

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.

Source

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".

Implementations on Foreign Types§

Source§

impl<S: NamespaceStore + ?Sized> NamespaceStore for Arc<S>

Source§

fn capabilities(&self) -> StoreCapabilities

Source§

async fn get( &self, p: &Partition, key: &Key, ) -> Result<Option<Value>, StoreError>

Source§

async fn has(&self, p: &Partition, key: &Key) -> Result<bool, StoreError>

Source§

async fn get_many( &self, p: &Partition, keys: &[Key], ) -> Result<Vec<Option<Value>>, StoreError>

Source§

async fn scan( &self, p: &Partition, start: &Key, end: &Key, after: Option<&Cursor>, limit: u32, ) -> Result<ScanPage, StoreError>

Source§

async fn scan_many( &self, p: &Partition, ranges: &[RangeScan], ) -> Result<Vec<ScanPage>, StoreError>

Source§

async fn apply( &self, p: &Partition, batch: Batch, ) -> Result<BatchOutcome, StoreError>

Source§

async fn stats(&self, p: &Partition) -> Result<PartitionStats, StoreError>

Source§

async fn probe(&self) -> Result<(), StoreError>

Implementors§