Skip to main content

boatramp_node/
backends.rs

1//! Backend-selection enums for a node's blob + KV stores (node-library N2b).
2//!
3//! These are the domain types the store-construction assembly dispatches on. They
4//! live here (not the binary) so the assembly can move into the library; the
5//! `clap::ValueEnum` derive is behind the optional `clap` feature so the CLI
6//! binary uses them directly in its args, while a non-CLI embedder never pulls
7//! clap. (`build_kv`/`build_blobs` join this module as they migrate off the
8//! binary's `ServeArgs`.)
9
10use std::path::Path;
11use std::sync::Arc;
12
13use boatramp_core::kv::{KvStore, MemoryKv};
14
15use crate::error::Result;
16
17/// Blob (file-content) backend.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
20pub enum BlobBackend {
21    /// Local filesystem (`<data-dir>/blobs`).
22    Fs,
23    /// S3-compatible object store (requires `--features s3`).
24    S3,
25    /// Google Cloud Storage (requires `--features gcs`).
26    Gcs,
27    /// Azure Blob Storage (requires `--features azure`).
28    Azure,
29}
30
31/// Metadata (manifest + pointer) backend.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
34pub enum KvBackend {
35    /// Transactional LSM over object storage; durable local default
36    /// (`<data-dir>/kv-slate`). Requires `--features slatedb` (on by default).
37    Slatedb,
38    /// In-memory (ephemeral; lost on restart).
39    Memory,
40    /// Cloudflare KV over REST (requires `--features cloudflare-kv`).
41    Cloudflare,
42}
43
44/// Flush interval for the control-plane SlateDB store: tiny, so a control-plane
45/// write is durable almost immediately (correctness over throughput).
46pub const CONTROL_PLANE_FLUSH: std::time::Duration = std::time::Duration::from_millis(5);
47
48/// Where the SlateDB control-plane store lives when it runs on an S3-compatible
49/// object store (Cloudflare R2) instead of local disk — the durable, remote-state
50/// deployment. Credentials come from the ambient AWS environment
51/// (`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`), matching the S3 blob backend.
52#[derive(Debug, Clone)]
53pub struct SlateKvS3 {
54    /// The bucket the store lives in (shared with S3 blobs, under `prefix`).
55    pub bucket: String,
56    /// Custom endpoint (R2: `https://<account>.r2.cloudflarestorage.com`).
57    pub endpoint: Option<String>,
58    /// Region (R2 uses `auto`).
59    pub region: Option<String>,
60    /// Use path-style addressing (R2 accepts it).
61    pub path_style: bool,
62    /// Key prefix within the bucket (keeps the LSM files apart from the blobs).
63    pub prefix: String,
64}
65
66/// Build the metadata KV store for the selected [`KvBackend`]. When `slate_s3` is
67/// set (and the backend is SlateDB), the store runs on R2/S3 (durable across a
68/// scale-to-zero container stop) rather than the local `data_dir`.
69pub async fn build_kv(
70    kv: KvBackend,
71    data_dir: &Path,
72    slate_s3: Option<&SlateKvS3>,
73) -> Result<Arc<dyn KvStore>> {
74    match kv {
75        KvBackend::Slatedb => build_slatedb_kv(data_dir, slate_s3).await,
76        KvBackend::Memory => Ok(Arc::new(MemoryKv::new())),
77        KvBackend::Cloudflare => build_cloudflare_kv(),
78    }
79}
80
81#[cfg(feature = "slatedb")]
82async fn build_slatedb_kv(
83    data_dir: &Path,
84    slate_s3: Option<&SlateKvS3>,
85) -> Result<Arc<dyn KvStore>> {
86    match slate_s3 {
87        Some(s3) => Ok(Arc::new(
88            boatramp_storage::SlateKv::open_s3_with_flush(
89                &boatramp_storage::S3StoreConfig {
90                    bucket: s3.bucket.clone(),
91                    endpoint: s3.endpoint.clone(),
92                    region: s3.region.clone(),
93                    path_style: s3.path_style,
94                },
95                &s3.prefix,
96                CONTROL_PLANE_FLUSH,
97            )
98            .await?,
99        )),
100        None => Ok(Arc::new(
101            boatramp_storage::SlateKv::open_local_with_flush(
102                data_dir.join("kv-slate"),
103                CONTROL_PLANE_FLUSH,
104            )
105            .await?,
106        )),
107    }
108}
109
110#[cfg(not(feature = "slatedb"))]
111async fn build_slatedb_kv(
112    _data_dir: &Path,
113    _slate_s3: Option<&SlateKvS3>,
114) -> Result<Arc<dyn KvStore>> {
115    Err(crate::error::Error::NoSlatedbSupport)
116}
117
118#[cfg(feature = "cloudflare-kv")]
119fn build_cloudflare_kv() -> Result<Arc<dyn KvStore>> {
120    Ok(Arc::new(boatramp_storage::CloudflareKv::from_env()?))
121}
122
123#[cfg(not(feature = "cloudflare-kv"))]
124fn build_cloudflare_kv() -> Result<Arc<dyn KvStore>> {
125    Err(crate::error::Error::NoCloudflareKvSupport)
126}