Skip to main content

spacedb_consistency/
tier.rs

1//! Consistency tiers and the per-field schema that selects them.
2//!
3//! In a partition-prone world one global consistency setting is always wrong:
4//! most data wants availability, a little wants linearizability, and only the
5//! developer knows which is which. So consistency is a **per-field choice**,
6//! declared in the schema.
7
8use std::collections::HashMap;
9
10/// The consistency a field is served at.
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum Tier {
13    /// CRDT, the default for ~95% of data: always writable offline, auto-merging,
14    /// never blocks. Content, profiles, tags, feeds, tallies.
15    Convergent,
16    /// Session causal+: read-your-writes and monotonic reads via a causal token
17    /// over state vectors — cheap and partition-tolerant, no consensus.
18    Causal,
19    /// Linearizable, opt-in and deliberately expensive: uniqueness, non-negative
20    /// invariants, money. A quorum that **fails safe** under partition.
21    Strong,
22}
23
24/// Which tier each field is served at; everything defaults to [`Tier::Convergent`].
25#[derive(Clone, Debug)]
26pub struct ConsistencySchema {
27    default: Tier,
28    fields: HashMap<String, Tier>,
29}
30
31impl Default for ConsistencySchema {
32    fn default() -> Self {
33        Self {
34            default: Tier::Convergent,
35            fields: HashMap::new(),
36        }
37    }
38}
39
40impl ConsistencySchema {
41    /// A schema where every field defaults to [`Tier::Convergent`].
42    pub fn new() -> Self {
43        Self::default()
44    }
45
46    /// Annotate `field` with `tier` (builder style).
47    pub fn with_field(mut self, field: impl Into<String>, tier: Tier) -> Self {
48        self.fields.insert(field.into(), tier);
49        self
50    }
51
52    /// The tier `field` is served at — its annotation, or the default.
53    pub fn tier_of(&self, field: &str) -> Tier {
54        self.fields.get(field).copied().unwrap_or(self.default)
55    }
56
57    /// The default tier.
58    pub fn default_tier(&self) -> Tier {
59        self.default
60    }
61}